開發網站時常常會用到跨域資源共享(簡稱cors,後面使用簡稱)來解決跨域問題,可是在使用cors的時候,http請求會被劃分爲兩類,簡單請求和複雜請求,而這兩種請求的區別主要在因而否會觸發cors預檢請求。前端
首先咱們要明白cors的原理(引自MDN):ios
跨域資源共享標準新增了一組 HTTP 首部字段,容許服務器聲明哪些源站經過瀏覽器有權限訪問哪些資源。另外,規範要求,對那些可能對服務器數據產生反作用的 HTTP 請求方法(特別是 GET 之外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否容許該跨域請求。服務器確認容許以後,才發起實際的 HTTP 請求。在預檢請求的返回中,服務器端也能夠通知客戶端,是否須要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關數據)express
從上面的文字中咱們獲得以下信息:json
一、跨域資源共享標準新增了一組 HTTP 首部字段,服務器經過這些字段來控制瀏覽器有權訪問哪些資源。axios
二、爲了安全起見請求方式分爲兩類,一類不會預先發送options請求,一些會預先發送options請求。後端
三、 GET 之外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求會觸發options請求。跨域
四、服務器驗證OPTIONS完成後纔會容許發送世界的http請求。瀏覽器
不會觸發http預檢請求的即是簡單請求,想法可以觸發http預檢請求的即是複雜請求。緩存
那麼有哪些簡單請求呢?如下是來自MDN官方引用:安全
一、使用下列方法之一:
GET、
POST、
HEAD。
二、不得人爲設置該集合以外的其餘首部字段。該集合爲:
Accept
Accept-Language
Content-Language
Content-Type
三、Content-Type 的值僅限於下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
四、請求中的任意XMLHttpRequestUpload 對象均沒有註冊任何事件監聽器;XMLHttpRequestUpload 對象可使用 XMLHttpRequest.upload 屬性訪問
五、請求中沒有使用 ReadableStream 對象
那什麼是複雜請求呢,除了簡單請求都是複雜請求。
簡單請求的發送從代碼上來看和普通的XHR沒太大區別,可是HTTP頭當中要求老是包含一個域(Origin)的信息。該域包含協議名、地址以及一個可選的端口。不過這一項實際上由瀏覽器代爲發送,並非開發者代碼能夠觸及到的。
簡單請求的部分響應頭及解釋以下:
Access-Control-Allow-Origin(必含)- 不可省略,不然請求按失敗處理。該項控制數據的可見範圍,若是但願數據對任何人均可見,能夠填寫"*"。 Access-Control-Allow-Credentials(可選) – 該項標誌着請求當中是否包含cookies信息,只有一個可選值:true(必爲小寫)。若是不包含cookies,請略去該項,而不是填寫false。這一項與XmlHttpRequest2對象當中的withCredentials屬性應保持一致,即withCredentials爲true時該項也爲true;withCredentials爲false時,省略該項不寫。反之則致使請求失敗。 Access-Control-Expose-Headers(可選) – 該項肯定XmlHttpRequest2對象當中getResponseHeader()方法所能得到的額外信息。一般狀況下,getResponseHeader()方法只能得到以下的信息: Cache-Control Content-Language Content-Type Expires Last-Modified Pragma 當你須要訪問額外的信息時,就須要在這一項當中填寫並以逗號進行分隔
若是僅僅是簡單請求,那麼即使不用CORS也沒有什麼大不了,但CORS的複雜請求就令CORS顯得更加有用了。簡單來講,任何不知足上述簡單請求要求的請求,都屬於複雜請求。好比說你須要發送PUT、DELETE等HTTP動做,或者發送Content-Type: application/json的內容。
複雜請求表面上看起來和簡單請求使用上差很少,但實際上瀏覽器發送了不止一個請求。其中最早發送的是一種"預請求",此時做爲服務端,也須要返回"預迴應"做爲響應。預請求其實是對服務端的一種權限請求,只有當預請求成功返回,實際請求才開始執行。
預請求以OPTIONS形式發送,當中一樣包含域,而且還包含了兩項CORS特有的內容
Access-Control-Request-Method – 該項內容是實際請求的種類,能夠是GET、POST之類的簡單請求,也能夠是PUT、DELETE等等。 Access-Control-Request-Headers – 該項是一個以逗號分隔的列表,當中是複雜請求所使用的頭部。
顯而易見,這個預請求實際上就是在爲以後的實際請求發送一個權限請求,在預迴應返回的內容當中,服務端應當對這兩項進行回覆,以讓瀏覽器肯定請求是否可以成功完成。
複雜請求的部分響應頭及解釋以下:
Access-Control-Allow-Origin(必含) – 和簡單請求同樣的,必須包含一個域。 Access-Control-Allow-Methods(必含) – 這是對預請求當中Access-Control-Request-Method的回覆,這一回復將是一個以逗號分隔的列表。儘管客戶端或許只請求某一方法,但服務端仍然能夠返回全部容許的方法,以便客戶端將其緩存。 Access-Control-Allow-Headers(當預請求中包含Access-Control-Request-Headers時必須包含) – 這是對預請求當中Access-Control-Request-Headers的回覆,和上面同樣是以逗號分隔的列表,能夠返回全部支持的頭部。這裏在實際使用中有遇到,全部支持的頭部一時可能不能徹底寫出來,而又不想在這一層作過多的判斷,不要緊,事實上經過request的header能夠直接取到Access-Control-Request-Headers,直接把對應的value設置到Access-Control-Allow-Headers便可。 Access-Control-Allow-Credentials(可選) – 和簡單請求當中做用相同 Access-Control-Max-Age(可選) – 以秒爲單位的緩存時間。預請求的的發送並不是免費午飯,容許時應當儘量緩存。
理論聊完以後,我們來看一下實踐,首先啓動兩個服務,一個端口爲3000,的靜態資源服務器,用於請求接口,另外一臺端口爲5000的接口服務器,如圖所示:
後端接口服務器代碼以下:
const express = require("express");
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
// 實現CORS
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,DELETE');
res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization");
res.header("cache-control", "no-cache");
res.header("content-type", "application/json; charset=utf-8");
res.header("ETag", '');
next();
});
app.post("/p",(req,res)=>{
res.send(req.body)
})
app.listen(5000,()=>{
console.log("5000")
})
前端請求資源腳本代碼以下:
axios.post("http://localhost:5000/p",{name:"zs",age:"18"}).then((data)=>{
console.log(data.data);
})
咱們用axios這個http請求庫發送了一個post請求,axios發送post請求默認會把數據轉化爲json格式,而且會默認設置請求頭:Content-Type:application/json,很顯然這是一個複雜請求,這樣的話,會觸發options請求。
咱們分別啓動兩個服務,並打開瀏覽器,訪問頁面,加載請求接口腳本,觀察network如圖:
咱們看到,代碼中命名只發送了一次異步請求爲何顯示兩次呢?詳細截圖以下:
咱們看到確實發送了兩次請求一次爲OPTIONS一次爲POST,而咱們代碼中並無處理對OPTIONS請求的響應處理,因此上面服務端代碼是不合理的,綜合考慮,OPTIONS請求並會對實際http請求差生影響,因此咱們統一的對OPTIONS請求返回204,服務端負責支持CORS的中間件修正代碼以下:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'OPTIONS,GET,POST,PUT,DELETE');
res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization");
res.header("cache-control", "no-cache");
res.header("content-type", "application/json; charset=utf-8");
res.header("ETag", '');
//header頭信息設置結束後,結束程序往下執行,返回
if(req.method.toLocaleLowerCase() === 'options'){
res.status(204);
return res.json({}); //直接返回空數據,結束這次請求
}else{
next();
}
});
總結一下:
1. 簡單請求:
知足一下兩個條件的請求。
(1) 請求方法是如下三種方法之一:
(2)HTTP的頭信息不超出如下幾種字段:
2. 複雜請求:
非簡單請求就是複雜請求。
非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。
非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight)。
預檢請求爲OPTIONS請求,用於向服務器請求權限信息的。
預檢請求被成功響應後,纔會發出真實請求,攜帶真實數據。
axios默認請求就是application/json,因此不須要本身加上頭部(不須要在config中加headers),因此老是會發出options請求的,看看是否是配置的時候加了沒必要要的headers配置項。 另外,若是真的須要預檢,後臺也須要進行設置,容許options請求。