Sanic框架基礎之解決CORS跨域
在前後端分離的情況下,CORS是必然要解決的問題。那什麼是CORS呢?
CORS是跨域資源共享的英文單詞縮寫,CORS是瀏覽器的一種策略,出於安全原因,瀏覽器限制從指令碼內發起的跨源HTTP請求響應,比如在http://127.0.0.1:8080的頁面上向http://127.0.0.1:80通過ajax發出一個post請求,此時80埠後端可以收到http請求,但瀏覽器會主動攔截響應結果。
那麼怎麼解決跨域問題呢,有兩個方案
- 通過後端伺服器設定正確的響應頭,來告訴瀏覽器我允許這次跨域,請求瀏覽器放行
- 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框架都可以簡單的處理跨域問題。