Sanic框架基礎之解決CORS跨域


在前后端分離的情況下,CORS是必然要解決的問題。那什么是CORS呢?
CORS是跨域資源共享的英文單詞縮寫,CORS是瀏覽器的一種策略,出於安全原因,瀏覽器限制從腳本內發起的跨源HTTP請求響應,比如在http://127.0.0.1:8080的頁面上向http://127.0.0.1:80通過ajax發出一個post請求,此時80端口后端可以收到http請求,但瀏覽器會主動攔截響應結果。

那么怎么解決跨域問題呢,有兩個方案

  1. 通過后端服務器設置正確的響應頭,來告訴瀏覽器我允許這次跨域,請求瀏覽器放行
  2. JSONP

本文主要使用第一個方案,所以先簡單講講JSONP,大家肯定都用過script標簽,一些常用的前端框架如jquery,我們可以直接使用對方提供的url,瀏覽器也不會攔截這些內容,也就是說瀏覽器對script標簽的url是不存在跨域訪問控制的,基於這一特性,我們將一個跨域請求包裝在script標簽內,這樣就可以規避CORS的攔截。那么問題在於我通過script請求獲得的數據要如何操作,通常的操作是通過定義回調函數名,后端將回調函數和實際返回的數據做字符串拼接,前端收到數據后直接執行前端定義好的方法。

舉個栗子 我有一個api http://127.0.0.1:8080 正常響應的內容{'msg': 'hello'} 允許傳入一個參數callback 假如callback=printf 那么響應內容變成printf({'msg': 'hello'})
前端域名http://127.0.0.1 現在向訪問前面的api 彈出msg對應的內容 那么在前端使用jsonp的方式應該是這樣的

<script>
function printf(data) {
    alert(data.msg)
}
</script>
// 通過js代碼生成一個這樣的<script>標簽
<script src="http://127.0.0.1:8080?callback=printf"></script>

所以JSONP的核心就是基於script的特性,通過前端的回調和后端的字符串拼接來避過CORS策略。
JSONP的問題在於scipt標簽只能get請求,不能解決post、put等其他請求方式的問題

JSONP是通過另辟蹊徑來繞過CORS,那么有沒有一種直面CORS請求瀏覽器放行的策略呢,當然也是有的
我們可以通過響應頭來告訴瀏覽器,這個響應允許某個域接受,如果你碰到了這個域你就放行吧,關於CORS所規范的響應頭如下(參考資料

  • Access-Control-Allow-Origin
    參數的值指定了允許訪問該資源的外域 URI,服務器可以指定該字段的值為通配符"*"
  • Access-Control-Expose-Headers
    在跨域訪問時,XMLHttpRequest對象的getResponseHeader()方法只能拿到一些最基本的響應頭,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要訪問其他頭,則需要服務器設置本響應頭。
  • Access-Control-Max-Age
    指定了預檢請求的結果能夠被緩存多久
  • Access-Control-Allow-Credentials
    指定了當瀏覽器的credentials設置為true時是否允許瀏覽器讀取response的內容
  • Access-Control-Allow-Methods
    用於預檢請求的響應,其指明了實際請求所允許使用的 HTTP 方法
  • Access-Control-Allow-Headers
    用於預檢請求的響應,其指明了實際請求中允許攜帶的首部字段

簡單說說前面沒有聊到的預檢概念:
瀏覽器將請求分成了簡單請求和復雜請求兩種類別,對於復雜請求,瀏覽器首先會有一個"預檢"的行為,具體而言就是先向url發起一次OPTIONS請求,這個請求被稱為預檢。后端收到預檢請求后,應該響應前面規定的幾個響應頭,來告訴瀏覽器后端允許的一些跨域策略。

到現在,要解決跨域問題就比較明朗了,前端不需要任何操作,后端注意兩個點即可

  • 實現OPTIONS預檢請求響應
  • 在正常請求響應中添加Access-Control-Allow-Origin響應頭

回到應用,現在就以Sanic為例,來看看如何解決跨域(逐漸想起標題...)

首先在Sanic的生命周期中,有一點我是比較失望的,流程大致如下:
http請求——Sanic解析request——匹配路由——請求中間件——視圖函數——響應中間件——http響應

Sanic在匹配路由中會檢測是否存在對應的請求方法,如果沒有直接響應405,根本不走后面的中間件了,這意味着你不能使用中間件來實現所有路由表上的預檢請求,那么在注冊路由的時候,就一定要顯式申明OPTIONS或在CBV中實現options方法,之后我們才可以通過中間件來實現功能

@app.middleware("request")
def cors_middle_req(request: Request):
    """路由需要啟用OPTIONS方法"""
    if request.method.lower() == 'options':
        allow_headers = [
            'Authorization',
            'content-type'
        ]
        headers = {
            'Access-Control-Allow-Methods':
                ', '.join(request.app.router.get_supported_methods(request.path)),
            'Access-Control-Max-Age': '86400',
            'Access-Control-Allow-Headers': ', '.join(allow_headers),
        }
        return HTTPResponse('', headers=headers)

@app.middleware("response")
def cors_middle_res(request: Request, response: HTTPResponse):
    """跨域處理"""
    allow_origin = '*'
    response.headers.update(
        {
            'Access-Control-Allow-Origin': allow_origin,
        }
    )

前一個請求中間件cors_middle_req用於攔截所有OPTIONS方法,它在設置三個跨域請求頭后直接返回HTTPResponse對象,在這個中間件中,告知了瀏覽器允許的請求方式和請求頭,並設置了一個24小時的緩存(86400/3600)時間
后一個響應中間件cors_middle_res用於處理所有響應請求,在響應頭中加入Access-Control-Allow-Origin來允許跨域,allow_origin可以改成指定域名
在整個過程中我們使用到了6個規范響應頭中最常用的4個,剩下兩個響應頭大家可以自行去了解用途和適用場景

至此就解決了Sanic的跨域問題,整套流程梳理起來,當你了解了相關規范后其實你會發現並不復雜,任何web框架都可以簡單的處理跨域問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM