一、場景
今天在監測跨域代碼時發現,在調用后端接口的時候會出現兩次請求:OPTIONS請求和POST請求。代碼如下:
/// <summary> /// 自定義中間件要執行的邏輯 /// </summary> /// <param name="context"></param> /// <returns></returns> public async Task Invoke(HttpContext context) { context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); context.Response.Headers.Add("Access-Control-Allow-Headers", "*"); context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); //若為OPTIONS跨域請求則直接返回,不進入后續管道 if (context.Request.Method.ToUpper() != "OPTIONS") await _next(context);//把context傳進去執行下一個中間件 }
二、原因
XMLHttpRequest會遵守同源策略(same-origin policy),即腳本只能訪問相同協議/相同主機名/相同端口的資源。 突破這個限制的情況叫做跨域,此時需要遵守跨域資源共享標准CORS(Cross-Origin Resource Sharing)機制。瀏覽器將CORS請求分為兩類:簡單跨域請求(simple request)和非簡單跨域請求(not-simple-request)。簡單請求瀏覽器請求不會觸發預檢請求,而非簡單請求會觸發預檢請求。
三、跨域請求
跨域請求分為簡單跨域請求和非簡單跨域請求,這兩種方式是怎么區分的呢?
1、簡單跨域請求
(1)同時滿足下列以下條件,就屬於簡單請求,否則屬於非簡單請求
- 請求方式只能是:GET、POST、HEAD
- HTTP請求頭限制以下幾種字段(不得人為設置該集合之外的其他首部字段):
Accept、Accept-Language、Content-Language、Content-Type(需要注意額外的限制)、DPR、Downlink、Save-Data、Viewport-Width、Width
- Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
- 請求中的任意XMLHttpRequestUpload 對象均沒有注冊任何事件監聽器;XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload 屬性訪問。
- 請求中沒有使用 ReadableStream 對象。
(2)簡單請求瀏覽器請求不會觸發預檢請求
2、非簡單跨域請求
非簡單請求會在正式通信之前,增加一次HTTP請求,稱之為預檢請求。瀏覽器會先發起OPTIONS方法到服務器,以獲知服務器是否允許該實際請求。
四、如何避免非簡單跨域請求
我們通過上邊已經知道,只要滿足簡單請求的條件就可以避免發起OPTIONS請求,但是在實際開發中,簡單請求肯定不會滿足需求,比如以下請求:
- 我們系統請求中除了GET/POST還有PUT,DELETE
- 系統做了權限認證,請求頭里需要帶有用戶驗證信息
- 我們的Content-Type絕大多數是application/json
然后只能寄希望於減少發起OPTIONS請求的次數,也就是說還是會用,但不是每次都用,方法命令如下:
Access-Control-Max-Age:(number) 數值代表預檢請求的返回結果 可以被緩存多久,單位是秒
例如將預檢請求的結果緩存10分鍾:
/// <summary> /// 自定義中間件要執行的邏輯 /// </summary> /// <param name="context"></param> /// <returns></returns> public async Task Invoke(HttpContext context) { context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); context.Response.Headers.Add("Access-Control-Allow-Headers", "*"); context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); context.Response.Headers.Add("Access-Control-Max-Age", "600"); //若為OPTIONS跨域請求則直接返回,不進入后續管道 if (context.Request.Method.ToUpper() != "OPTIONS") await _next(context);//把context傳進去執行下一個中間件 }
注意:
- 不同瀏覽器有不同的上限,在Firefox中上限是24h,而在Chromium 中則是10min,Chromium同時規定了一個默認值 5 秒。如果值為 -1,則表示禁用緩存,每一次請求都需要提供預檢請求。
- Access-Control-Max-Age方法對完全一樣的url的緩存設置生效,多一個參數也視為不同url。也就是說,如果設置了10分鍾的緩存,在10分鍾內,所有請求第一次會產生options請求,第二次以及第二次以后就只發送真正的請求了。