摘要:目前代碼如下到現在為止,可以對跨域請求進行響應了,但是該域下的不會被攜帶在請求頭中。調整后順序如下這樣就減少了多余的響應頭。
實現跨域ajax請求的方式有很多,其中一個是利用CORS,而這個方法關鍵是在服務器端進行配置。
本文僅對能夠完成正常跨域ajax響應的,最基本的配置進行說明(深層次的配置我也不會)。
CORS將請求分為簡單請求和非簡單請求,可以簡單的認為,簡單請求就是沒有加上額外請求頭部的get和post請求,并且如果是post請求,請求格式不能是application/json(因為我對這一塊理解不深如果錯誤希望能有人指出錯誤并提出修改意見)。而其余的,put、post請求,Content-Type為application/json的請求,以及帶有自定義的請求頭部的請求,就為非簡單請求。
簡單請求的配置十分簡單,如果只是完成響應就達到目的的話,僅需配置響應頭部的Access-Control-Allow-Origin即可。
如果我們在http://localhost:3000 域名下想要訪問 http://127.0.0.1:3001 域名。可以做如下配置:
app.use(async (ctx, next) => { ctx.set("Access-Control-Allow-Origin", "http://localhost:3000"); await next(); });
然后用ajax發起一個簡單請求,例如post請求,就可以輕松的得到服務器正確響應了。
實驗代碼如下:
$.ajax({ type: "post", url: "http://127.0.0.1:3001/async-post" }).done(data => { console.log(data); })
服務器端代碼:
router.post("/async-post",async ctx => { ctx.body = { code: "1", msg: "succ" } });
然后就能得到正確的響應信息了。
這時候如果看一下請求和響應的頭部信息,會發現請求頭部多了個origin(還有一個referer為發出請求的url地址),而響應頭部多了個Access-Control-Allow-Origin。
現在可以發送簡單請求了,但是要想發送非簡單請求還是需要其他的配置。
當第一次發出非簡單請求的時候,實際上會發出兩個請求,第一次發出的是preflight request,這個請求的請求方法是OPTIONS,這個請求是否通過決定了這一個種類的非簡單請求是否能成功得到響應。
為了能在服務器匹配到這個OPTIONS類型的請求,因此需要自己做一個中間件來進行匹配,并給出響應使得這個預檢能夠通過。
app.use(async (ctx, next) => { if (ctx.method === "OPTIONS") { ctx.body = ""; } await next(); });
這樣OPTIONS請求就能夠通過了。
如果檢查一下preflight request的請求頭部,會發現多了兩個請求頭。
Access-Control-Request-Method: PUT Origin: http://localhost:3000
要通過這兩個頭部信息與服務器進行協商,看是否符合服務器應答條件。
很容易理解,既然請求頭多了兩個信息,響應頭自然也應該有兩個信息相對應,這兩個信息如下:
Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Methods: PUT,DELETE,POST,GET
第一條信息和origin相同因此通過。第二條信息對應Access-Controll-Request-Method,如果在請求的方式包含在服務器允許的響應方式之中,因此這條也通過。兩個約束條件都滿足了,所以可以成功的發起請求。
至此為止,相當于僅僅完成了預檢,還沒發送真正的請求呢。
真正的請求當然也成功獲得了響應,并且響應頭如下(省略不重要部分)
Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Methods: PUT,DELETE,POST,GET
請求頭如下:
Origin: http://localhost:3000
這就很顯而易見了,響應頭部信息是我們在服務器設定的,因此是這樣。
而客戶端因為剛才已經預檢過了,所以不需要再發Access-Control-Request-Method這個請求頭了。
這個例子的代碼如下:
$.ajax({ type: "put", url: "http://127.0.0.1:3001/put" }).done(data => { console.log(data); });
服務器代碼:
app.use(async (ctx, next) => { ctx.set("Access-Control-Allow-Origin", "http://localhost:3000"); ctx.set("Access-Control-Allow-Methods", "PUT,DELETE,POST,GET"); await next(); });
至此我們完成了能夠正確進行跨域ajax響應的基本配置,還有一些可以進一步配置的東西。
比如,到目前為止,每一次非簡單請求都會實際上發出兩次請求,一次預檢一次真正請求,這就比較損失性能了。為了能不發預檢請求,可以對如下響應頭進行配置。
Access-Control-Max-Age: 86400
這個響應頭的意義在于,設置一個相對時間,在該非簡單請求在服務器端通過檢驗的那一刻起,當流逝的時間的毫秒數不足Access-Control-Max-Age時,就不需要再進行預檢,可以直接發送一次請求。
當然,簡單請求時沒有預檢的,因此這條代碼對簡單請求沒有意義。
目前代碼如下:
app.use(async (ctx, next) => { ctx.set("Access-Control-Allow-Origin", "http://localhost:3000"); ctx.set("Access-Control-Allow-Methods", "PUT,DELETE,POST,GET"); ctx.set("Access-Control-Max-Age", 3600 * 24); await next(); });
到現在為止,可以對跨域ajax請求進行響應了,但是該域下的cookie不會被攜帶在請求頭中。如果想要帶著cookie到服務器,并且允許服務器對cookie進一步設置,還需要進行進一步的配置。
為了便于后續的檢測,我們預先在http://127.0.0.1:3001這個域名下設置兩個cookie。注意不要錯誤把cookie設置成中文(剛才我就設置成了中文,結果報錯,半天沒找到出錯原因)
然后我們要做兩步,第一步設置響應頭Access-Control-Allow-Credentials為true,然后在客戶端設置xhr對象的withCredentials屬性為true。
客戶端代碼如下:
$.ajax({ type: "put", url: "http://127.0.0.1:3001/put", data: { name: "黃天浩", age: 20 }, xhrFields: { withCredentials: true } }).done(data => { console.log(data); });
服務端如下:
app.use(async (ctx, next) => { ctx.set("Access-Control-Allow-Origin", "http://localhost:3000"); ctx.set("Access-Control-Allow-Methods", "PUT,DELETE,POST,GET"); ctx.set("Access-Control-Allow-Credentials", true); await next(); });
這時就可以帶著cookie到服務器了,并且服務器也可以對cookie進行改動。但是cookie仍是http://127.0.0.1:3001域名下的cookie,無論怎么操作都在該域名下,無法訪問其他域名下的cookie。
現在為止CORS的基本功能已經都提到過了。
一開始我不知道怎么給Access-Control-Allow-Origin,后來經人提醒,發現可以寫一個白名單數組,然后每次接到請求時判斷origin是否在白名單數組中,然后動態的設置Access-Control-Allow-Origin,代碼如下:
app.use(async (ctx, next) => { if (ctx.request.header.origin !== ctx.origin && whiteList.includes(ctx.request.header.origin)) { ctx.set("Access-Control-Allow-Origin", ctx.request.header.origin); ctx.set("Access-Control-Allow-Methods", "PUT,DELETE,POST,GET"); ctx.set("Access-Control-Allow-Credentials", true); ctx.set("Access-Control-Max-Age", 3600 * 24); } await next(); });
這樣就可以不用*通配符也可匹配多個origin了。
注意:ctx.origin與ctx.request.header.origin不同,ctx.origin是本服務器的域名,ctx.request.header.origin是發送請求的請求頭部的origin,二者不要混淆。
最后,我們再稍微調整一下自定義的中間件的結構,防止每次請求都返回Access-Control-Allow-Methods以及Access-Control-Max-Age,這兩個響應頭其實是沒有必要每次都返回的,只是第一次有預檢的時候返回就可以了。
調整后順序如下:
app.use(async (ctx, next) => { if (ctx.request.header.origin !== ctx.origin && whiteList.includes(ctx.request.header.origin)) { ctx.set("Access-Control-Allow-Origin", ctx.request.header.origin); ctx.set("Access-Control-Allow-Credentials", true); } await next(); }); app.use(async (ctx, next) => { if (ctx.method === "OPTIONS") { ctx.set("Access-Control-Allow-Methods", "PUT,DELETE,POST,GET"); ctx.set("Access-Control-Max-Age", 3600 * 24); ctx.body = ""; } await next(); });
這樣就減少了多余的響應頭。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93167.html
摘要:開發既是一個練習如何在開發環境中寫代碼的過程,反過來,也是一個驗證環境搭建的對不對好不好用的過程。前端調用后端接口示例為突出重點,排除干擾,方便理解。 寫在前面 這篇文章的主要目的是學會使用koa框架搭建web服務,從而提供一些后端接口,供前端調用。搭建這個環境的目的是: 前端工程師在跟后臺工程師商定了接口但還未聯調之前,涉及到向后端請求數據的功能能夠走前端工程師自己搭建的http路徑...
閱讀 1160·2021-11-24 09:38
閱讀 3609·2021-11-22 15:32
閱讀 3461·2019-08-30 15:54
閱讀 2573·2019-08-30 15:53
閱讀 1499·2019-08-30 15:52
閱讀 2531·2019-08-30 13:15
閱讀 1841·2019-08-29 12:21
閱讀 1402·2019-08-26 18:36