<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【面試必會(huì)】煩不勝煩的跨域問題?再見

          共 4860字,需瀏覽 10分鐘

           ·

          2020-10-18 21:02


          最近接了個(gè)外包項(xiàng)目,我負(fù)責(zé)后端,想著省事直接用 Django,吭哧吭哧寫了一天 API 后,拉了前端代碼下來聯(lián)調(diào),果不其然遇到了跨域問題。然而這個(gè)跨域有點(diǎn)詭異,折騰了一天時(shí)間,頂不住睡一覺,第二天一起床反而解決了。這個(gè)故事告訴我們,做到十二點(diǎn),就不做了,睡大覺!

          談到跨域,就不可避免的要講講CORS,這些到底都是啥?它們有什么區(qū)別?請聽我慢慢道來。

          跨域資源共享

          CORS (Cross-Origin Resource Sharing,跨域資源共享)是一個(gè)系統(tǒng),它由一系列傳輸?shù)?HTTP頭組成,這些HTTP頭決定瀏覽器是否阻止前端 JavaScript 代碼獲取跨域請求的響應(yīng)。

          簡而言之,在前后端分離的開發(fā)模式下,我們的前端在向后臺(tái)請求資源時(shí),由于瀏覽器的同源策略,默認(rèn)情況下我們是無法獲得資源的,這個(gè)時(shí)候,就會(huì)產(chǎn)生我們常說的跨域問題

          跨域問題

          如上圖所示,在瀏覽器的Console控制臺(tái)中,當(dāng)我們發(fā)送的請求遇到跨域問題時(shí),就會(huì)出現(xiàn)上圖的報(bào)錯(cuò)信息。

          瀏覽器的同源策略

          在了解跨域問題的解決辦法之前,我們先來看看瀏覽器的同源策略是怎么一回事:

          如下圖所示,Web document我們可以理解為前端,所在域?yàn)?code style="font-size: inherit;line-height: inherit;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background: rgb(248, 248, 248);">domain-a.com;當(dāng)它請求同域名下的Web server(即左上方的服務(wù)器)時(shí),它們的請求是同源請求(總被允許的),而當(dāng)它請求domain-b.com下的Web server時(shí),它們的請求是跨域請求(受 CORS 控制)

          瀏覽器的同源策略

          也就是說,我們的跨域問題本質(zhì)上不是一個(gè)問題,而是在跨域請求中,我們沒有用CORS控制我們的服務(wù)器允許該請求,那么解決辦法也就很簡單了,我們需要修改后臺(tái)的一些配置,使其允許來自前端的跨域請求

          我們前面也提到,CORS 由一系列的傳輸 HTTP 頭組成,實(shí)際上,跨域資源共享標(biāo)準(zhǔn)新增了一組 HTTP 首部字段,允許服務(wù)器聲明哪些源站通過瀏覽器有權(quán)限訪問哪些資源

          綜合來看,實(shí)際上我們要修改的配置,就是返回給前端的response上的頭(headers)

          回到故事的開始

          稍微查閱資料,我們在response的頭中加入:Access-Control-Allow-Origin: *;但事情并沒有這么簡單,在我的 Django 后臺(tái)中,我嘗試了自定義middleware、使用第三方的django-cors-headers、直接修改response返回值,發(fā)現(xiàn)居然都不起作用。翻遍全網(wǎng),來來去去都是這幾個(gè)辦法,迫于無奈,我打斷點(diǎn),Debug,甚至還瞎改了一通django-cors-headers的源碼,驗(yàn)證一些想法,最后發(fā)現(xiàn)的原因:前端的鍋

          注意下圖中注釋掉的部分,當(dāng)我將其注釋掉后,后端使用django-cors-headers的情況下,跨域問題就解決了,那么為什么加上注釋中的部分就會(huì)導(dǎo)致跨域問題呢?

          前端的請求構(gòu)造

          這里我們不得不再深究一下跨域的相關(guān)知識(shí),否則每次遇到,都只能找各種或斷斷續(xù)續(xù)、或版本不對的博客去看,問題沒解決,時(shí)間倒是浪費(fèi)了。

          深究跨域相關(guān)知識(shí)

          CORS規(guī)范要求,對那些可能對服務(wù)器數(shù)據(jù)產(chǎn)生副作用的 HTTP 請求方法(特別是 GET以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST請求),瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個(gè)預(yù)檢請求(preflight request),從而獲知服務(wù)端是否允許該跨域請求。服務(wù)器確認(rèn)允許之后,才發(fā)起實(shí)際的 HTTP 請求。

          從上述內(nèi)容中我們可以了解到,CORS僅針對部分請求會(huì)先發(fā)起一個(gè)預(yù)檢請求(OPTIONS),而對于其他的簡單請求則不需要這個(gè)過程。

          我們先來看看哪些情況是符合簡單請求的:

          簡單請求

          在本文指代:某些不會(huì)觸發(fā) CORS 預(yù)檢請求的請求。

          • 使用列出的三種方法之一:GET、POST、HEAD

          • Fetch 規(guī)范定義了對 CORS 安全的首部字段集合不得人為設(shè)置該集合之外的其他首部字段

            • Accept

            • Accept-Language

            • Content-Language

            • Content-Type (注意,第三點(diǎn)為對該字段的限制)

            • DPR

            • Downlink

            • Save-Data

            • Viewport-Width

            • Width

          • Content-Type的值僅限三種之一

            • text/plain

            • multipart/form-data

            • application/x-www-form-urlencoded

          • 請求中的任意XMLHttpRequestUpload 對象均沒有注冊任何事件監(jiān)聽器;XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload屬性訪問。

          • 請求中沒有使用 ReadableStream對象。

          需要滿足上述五個(gè)條件,才符合簡單請求的要求。我們?nèi)绻軐㈩A(yù)檢請求降為簡單請求,那么就能解決跨域問題。

          如何解決呢?對于簡單請求,我們只需要在返回的response中加入Access-Control-Allow-Origin控制頭,把前端所在域加入或者直接使用*,即可解決。

          跨域的簡單請求

          而回到故事中,前端發(fā)起的login請求屬于POST請求,Content-Type也是application/x-www-form-urlencoded符合要求,問題就出在這個(gè)headers上,這個(gè)自定義headers使得第二點(diǎn)不滿足,所以瀏覽器會(huì)先發(fā)起一個(gè)預(yù)檢請求,不同于簡單請求,包含預(yù)檢請求的處理方式較為麻煩,直接在response中加入控制頭是不足以解決跨域問題的。

          headers導(dǎo)致變?yōu)轭A(yù)檢請求

          我們再來看看詳細(xì)的預(yù)檢請求

          預(yù)檢請求

          與前述簡單請求不同,需預(yù)檢的請求要求必須首先使用 OPTIONS方法發(fā)起一個(gè)預(yù)檢請求到服務(wù)器,以獲知服務(wù)器是否允許該實(shí)際請求。預(yù)檢請求的使用,可以避免跨域請求對服務(wù)器的用戶數(shù)據(jù)產(chǎn)生未預(yù)期的影響。

          不滿足簡單請求的就是預(yù)檢請求,比如:PUTDELETE請求等等。

          如下圖所示,這是一個(gè)預(yù)檢請求的例子,我們的POST請求中包含了自定義的headersX_PINGOTHER: pingpong,因此先發(fā)送了一個(gè)OPTIONS請求:

          在得到的response中,我們可以看到:

          Access-Control-Allow-Origin中有Client的來源originhttp://foo.example

          Access-Control-Allow-Methods中有POST

          Access-Control-Allow-Headers中也有自定義的X-PINGOTHER

          因此Client得到允許,繼續(xù)向Server發(fā)送POST請求,最終獲得數(shù)據(jù)。

          另外,首部字段 Access-Control-Max-Age 表明該響應(yīng)的有效時(shí)間為 86400 秒,也就是 24 小時(shí)。在有效時(shí)間內(nèi),瀏覽器無須為同一請求再次發(fā)起預(yù)檢請求。請注意,瀏覽器自身維護(hù)了一個(gè)最大有效時(shí)間,如果該首部字段的值超過了最大有效時(shí)間,將不會(huì)生效。

          跨域的預(yù)檢請求

          回到故事,在此時(shí)我進(jìn)行了一個(gè)小實(shí)驗(yàn),我把前端中的headers的注釋取消掉,

          前端請求中取消注釋

          在后端的CORS控制中,在Access-Control-Allow-Headers中加入:

          CORS允許的控制頭

          此時(shí)我們重新測試請求,發(fā)現(xiàn)解決了跨域問題。

          總結(jié)

          在這里,我們先總結(jié)一下如何解決跨域問題:

          • 對于簡單請求,直接操作對應(yīng)的responseheaders,一般Access-Control-Allow-OriginAccess-Control-Allow-Methods就能夠解決;對于預(yù)檢請求,一般還需要Access-Control-Allow-Headers。【如果需要傳輸cookie,那么會(huì)涉及:Access-Control-Allow-Credentials

          • 檢查當(dāng)前的請求是簡單請求還是預(yù)檢請求,如果不是簡單請求,能否變?yōu)楹唵握埱螅?/p>

          • 如果當(dāng)前是預(yù)檢請求,那么在Chrome中使用F12->Network中檢查Request Headers,檢查是否有簡單請求中不允許的頭,記錄下來。

            下圖是我用另一個(gè)項(xiàng)目的前端來測試這個(gè)后端的跨域問題是否解決,在該前端項(xiàng)目中使用了一個(gè)第三方的Encoding-Type控制頭:

          注意自定義headers

          我們在后端的CORS控制中加入,成功解決。

          允許該自定義headers

          在后端服務(wù)中,我們往往會(huì)使用一些現(xiàn)成的CORS處理插件,比如Django中的django-cors-headers,那么只需按照其github頁面的配置方式,同時(shí)注意上述的自定義頭的控制,那么處理跨域問題非常簡單;如果不是使用現(xiàn)成的CORS處理插件,我們就需要對OPTIONS請求和各種請求的Response進(jìn)行處理,最好是統(tǒng)一處理,比如在Springboot中可以在AOP層統(tǒng)一處理;

          在更輕量的情況下,我們也可以直接對單一請求的response進(jìn)行處理:

          func?login(w?http.ResponseWriter,?r?*http.Request)?{
          ????w.Header().Set("Access-Control-Allow-Origin",?"*")?????????????//允許訪問所有域
          ????w.Header().Add("Access-Control-Allow-Headers",?"Content-Type")?//header的類型
          ????w.Header().Set("content-type",?"application/json")?//返回?cái)?shù)據(jù)格式是json
          ????resp?:=?`{"code":?"00",
          ??????????????"message":?"SUCCESS",
          ??????????????"describe":?"登錄成功"
          ?????????????}`


          ????type?JsonResp?struct?{
          ????????Code???????int???????????????`json:"code"`
          ????????Message????string????????????`json:"message"`
          ????????Describe???string????????????`json:"describe"`
          ????}
          ????var?smsresp?JsonResp
          ????temp?:=?[]byte(resp)
          ????json.Unmarshal(temp,?&smsresp)
          ????fmt.Fprintf(w,?string(temp))
          }

          上面是我之前用go語言編寫的一個(gè)loginAPI,我們在response中加入Access-Control-Allow-Origin用以解決跨域問題,非常直觀。

          讀者福利
          《程序員內(nèi)功修煉》第二版強(qiáng)勢來襲,匯總了高質(zhì)量的算法、計(jì)算機(jī)基礎(chǔ)文章并且每一篇文章,要嘛是漫畫講解,要嘛是對話講解,一步步引導(dǎo),要嘛是圖形并茂,如果你想學(xué)習(xí)算法,學(xué)習(xí)計(jì)算機(jī)基礎(chǔ),那么我決定這份 PDF,一定會(huì)讓你有所幫助。當(dāng)然,如果一是一位有那么點(diǎn)迷茫的在校生,相信我的個(gè)人經(jīng)歷,可以給你打一份雞血,讓你更好著去尋找自己的目標(biāo)。

          文章整體目錄

          如何獲取

          很簡單,在我的微信公眾號(hào)?帥地玩編程?回復(fù)?程序員內(nèi)功修煉?即可獲取《程序員內(nèi)功修煉》第一版和第二版的 PDF。

          推薦,推薦一個(gè) GitHub,這個(gè) GitHub 整理了幾百本常用技術(shù)PDF,絕大部分核心的技術(shù)書籍都可以在這里找到,GitHub地址:https://github.com/iamshuaidi/CS-Book(電腦打開體驗(yàn)更好),地址閱讀原文直達(dá)


          瀏覽 51
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲第一香蕉视频在线观看 | 亚洲四虎影院 | 情趣在线91蜜桃 | 精品人伦一区二区三电影 - 百度 麻豆网站-麻豆午夜在线-成人AV | aaaaaa免费 |