1. 程式人生 > >ajax跨域簡單請求與複雜請求

ajax跨域簡單請求與複雜請求

開發網站時經常會用到跨域資源共享(簡稱cors,後面使用簡稱)來解決跨域問題,但是在使用cors的時候,http請求會被劃分為兩類,簡單請求和複雜請求,而這兩種請求的區別主要在於是否會觸發cors預檢請求。

首先我們要明白cors的原理(引自MDN):

跨域資源共享標準新增了一組 HTTP 首部欄位,允許伺服器宣告哪些源站通過瀏覽器有許可權訪問哪些資源。另外,規範要求,對那些可能對伺服器資料產生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 型別的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。伺服器確認允許之後,才發起實際的 HTTP 請求。在預檢請求的返回中,伺服器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關資料)

從上面的文字中我們得到如下資訊:

1、跨域資源共享標準新增了一組 HTTP 首部欄位,伺服器通過這些欄位來控制瀏覽器有權訪問哪些資源。

2、為了安全起見請求方式分為兩類,一類不會預先發送options請求,一些會預先發送options請求。

3、 GET 以外的 HTTP 請求,或者搭配某些 MIME 型別的 POST 請求會觸發options請求。

4、伺服器驗證OPTIONS完成後才會允許傳送世界的http請求。

不會觸發http預檢請求的便是簡單請求,想法能夠觸發http預檢請求的便是複雜請求。

那麼有哪些簡單請求呢?以下是來自MDN官方引用:

1、使用下列方法之一:

GET、

POST、

HEAD。

2、不得人為設定該集合之外的其他首部欄位。該集合為:

Accept

Accept-Language

Content-Language

Content-Type

3、Content-Type 的值僅限於下列三者之一:

text/plain

multipart/form-data

application/x-www-form-urlencoded

4、請求中的任意XMLHttpRequestUpload 物件均沒有註冊任何事件監聽器;XMLHttpRequestUpload 物件可以使用 XMLHttpRequest.upload 屬性訪問

5、請求中沒有使用 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) 請求方法是以下三種方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的頭資訊不超出以下幾種欄位:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

2. 複雜請求:

非簡單請求就是複雜請求。

非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的型別是application/json。

非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。

預檢請求為OPTIONS請求,用於向伺服器請求許可權資訊的。

預檢請求被成功響應後,才會發出真實請求,攜帶真實資料。

axios預設請求就是application/json,所以不需要自己加上頭部(不需要在config中加headers),所以總是會發出options請求的,看看是不是配置的時候加了不必要的headers配置項。
另外,如果真的需要預檢,後臺也需要進行設定,允許options請求。

&n