從網絡鏈路到跨域問題

首先,得知道什麼是域。javascript

再首先,得先知道Web服務從訪問到收到數據並展示這個過程發生了什麼。html

IP

注:下文中 網絡空間 = TCP/IP網絡空間, IP = IP地址vue

IP自己是Internet Protocol的縮寫,是一種爲了計算機相互鏈接通訊而產生的協議,咱們在這就用它代指ip地址。java

每一個在線的網絡服務在網絡空間的真實存在形式都是以ip地址形式存在的,它屬於網絡七層模型中的網絡層,它是一個地址,用來識別網絡空間中互聯的主機和路由器,暫且認爲用ip地址能夠訪問到一個服務器。node

比如地球上的每一個地點都有一個經緯度,只要這個經緯度真實存在並有效可用,咱們根據這個經緯度就必定能找到這個地點,且經緯度是永久不變的。webpack

實際上ip地址的「永久不變」是指在這個ip對應的互聯網服務的生命週期內,其不會變化。ios

直接服務場景:假設某公司拉了一條電信專線服務,電信給其分配8個ip地址,其中3個爲廣播地址,剩餘5個配置且部署好對應的網絡服務,外部是能夠直接訪問到該公司所架構的網絡服務的。nginx

真實商業生產場景:假設在某雲服務廠商購買服務器,默認會分配有惟一ip,服務器重啓、重置...ip是不會變化的,但服務器若是到期了,服務器提供商會同時釋放掉ip資源,這個ip可能就會分配給其餘服務,亦或者回收這個ip,ip自己和服務沒有關係,但大多數的商業服務ip都是隨服務捆綁的。git

簡單說:ip是指向網絡空間具體某一處的惟一地址,但它並非永遠不變的。github

域名和DNS

像經緯度同樣,ip地址是一長串數字,不便於記憶,因此咱們須要一個相似地名同樣的別名,當咱們一旦說出別名,就知道它大概在哪,且無需care它的真實經緯度。

咱們之因此聽到地名就知道這個位置大概所在,是由於咱們大腦內已經存儲了這個地名和真實地點的關聯信息,暫且稱之爲咱們存儲的這塊用於關聯地點的數據爲「數據庫」。

人腦有限,咱們不可能記住全部的地名和地理位置,因此須要有一個容易專門來存儲這些關聯數據的數據庫,最好其能夠直接把咱們帶到目的地。

這就是DNS,全稱Domain Name System,他作的事情很簡單,就是將咱們輸入的ip別名(域名)經過數據庫解析爲ip地址返回。

實際上,瀏覽器或咱們發起請求的客戶端會根據DNS返回的ip去請求網絡資源,並返回解析展現。

然而,瀏覽器請求時如何知道域名使用的是哪家DNS服務商呢,因而瀏覽器便須要先把域名發送到本地配置的DNS服務商(本地域名服務器/Local DNS Server)那裏獲得該域名的NS(Name Server),而後再把該域名拿到Name Server去獲取IP,而後再向該IP請求數據。

實際上整個域名解析的鏈路十幾步不止,瀏覽器請求LDNS以前會對瀏覽器自己的DNS緩存和本機DNS緩存的判斷,判斷會根據命中狀況和TTL和其餘數據決定是否進入下一步。

LDNS若是查詢失敗,則會直接請求root DNS Servers,root DNS Servers只爲全球只有十三臺的gTLD(generic Top-Level DNS Server)進行服務,RDNS會返回域名所在的主域名服務器的地址,即對應的gTLD地址,而後繼續向gTLD發送請求以獲得NS地址,因而又回到了上一步。

一張圖來表示:

簡單說就是:DNS是一套完整的系統,這套系統作的事就是根據一個個的表去查對應的數據,最終返回一個目標IP。

跨域

該說域了;咱們能夠把域理解爲一個域名或IP所表明的範圍,好比訪問a.com指向了A服務器,訪問b.com指向了B服務器,咱們能夠認爲a和b是分開獨立的兩個域。

大多數狀況下,a.com b.com都應該是兩個沒有關係的單獨網絡服務,他們默認不該該產生關聯(靜態資源引用除外),起碼瀏覽器是這麼認爲的。

因此若是你在a.com下向b.com發起一個觸發瀏覽器安全機制的xhr的網絡請求,瀏覽器默認是會攔截的,並附贈一大串Error,他認爲你這麼作不安全,不容許跨域請求數據。

CORS

CORS(Cross-origin resource sharing)是一個W3C標準,全稱"跨域資源共享"。
它規定容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而解決跨域問題。

咱們先看瀏覽器的安全機制是怎樣的?

瀏覽器把異步請求(xhr/fetch)分爲兩類:

  • 簡單請求
  • 非簡單請求

簡單請求的條件:

  1. 請求方法是如下三種方法之一:

    • HEAD
    • GET
    • POST
  2. HTTP的頭信息不超出如下幾種字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限於三個值application/x-www-form-urlencodedmultipart/form-datatext/plain

只要不知足以上條件的請求均爲非簡單請求

簡單請求

簡單請求在發出時,瀏覽器會自動給請求頭加上Origin字段,該字段爲當前請求發出者所在的域(協議 + 域名 + 端口),
服務端根據請求headers裏的Origin來判斷是否容許請求者獲取資源,若是容許,服務端在返回時會在headers裏攜帶幾個特殊的字段,用於告知瀏覽器,容許這次請求,
不然,即便正常返回數據,瀏覽器檢測到無對應的容許跨域字段,也會在console throw Error,告知你跨域訪問失敗。

這幾個字段即是CORS標準中所實現的三個字段:

  1. Access-Control-Allow-Origin
    該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求。

  2. Access-Control-Allow-Credentials
    該字段可選。它的值是一個布爾值,表示是否容許發送Cookie。默認狀況下,Cookie不包括在CORS請求之中。設爲true,即表示服務器明確許可,Cookie能夠包含在請求中,一塊兒發給服務器。這個值也只能設爲true,若是服務器不要瀏覽器發送Cookie,刪除該字段便可。
    若是要發送Cookie,Access-Control-Allow-Origin就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。

  3. Access-Control-Expose-Headers
    該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏指定。

非簡單請求

非簡單請求通常出如今對服務端進行CUD操做的場景下,異源RESTful就是一種最典型的場景,會使用到PUTDELETEPATCH...等請求類型,且通常以application/json格式進行數據交互。

當瀏覽器把一個請求斷定爲非簡單請求,則其發出前,瀏覽器會預先對服務端發起一個OPTIONS類型的預檢(preflight)請求,同時也會加上Origin字段,
瀏覽器會根據這次預檢請求返回的響應頭來判斷,服務端是否容許本域跨域操做資源,若判斷爲容許,則瀏覽器當即發出自己要發出的請求,不然,控制檯拋出異常,中斷請求。

服務端應返回的響應頭應包含如下幾個CORS字段:

  1. Access-Control-Allow-Origin
    必需返回,同簡單請求中的含義。

  2. Access-Control-Allow-Methods
    該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。

  3. Access-Control-Allow-Headers
    若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。

  4. Access-Control-Allow-Credentials
    可選返回,同簡單請求中的含義。

  5. Access-Control-Max-Age
    該字段可選,用來指定本次預檢請求的有效期,單位爲秒。上面結果中,有效期是20天(1728000秒),即容許緩存該條迴應1728000秒(即20天),在此期間,不用發出另外一條預檢請求。

在每一次真正的數據請求時,Access-Control-Allow-Origin字段都是服務端必定會返回的。

爲避免頻繁的預檢請求下降效率,真實生產環境下,建議設置Access-Control-Max-Age字段。

解決方法

開發環境下解決方案

經過本機開啓代理實如今開發環境下將api轉化爲子路徑,Node.js、apache、nginx都可實現。

webpack版本代碼: 所有代碼

proxy: {
    '/api': {
        target: 'http://localhost:8000',
        secure: false,
        changeOrigin: true,
        pathRewrite: {
            '^/api': ''
        }
    }
},複製代碼

生產環境下解決方法

爲服務端設置預檢請求的響應及相關的CORS字段。Node.js版本代碼

誤區

JSONP只是在CORS未規範以前用於解決基本跨域的曲徑(奇技淫巧),其並不是解決跨域的真正途徑。

web開發者在使用vue-resource、axios...等各類異步庫時,可能會存在相似解決跨域的選項,其多是內部對簡單請求和非簡單請求進行的一些基本轉換,此類並不是真正解決跨域的方法,解決跨域務必須要服務端處理。

本文部份內容參考來源:

《跨域資源共享 CORS 詳解》

MDN - HTTP訪問控制(CORS)

W3C - HTTP/1.1: Method Definitions

原文地址:surmon.me/article/21

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息