這次帶給大家怎樣用Koa2框架的CORS完成跨域ajax請求,用Koa2框架的CORS完成跨域ajax請求的注意事項有哪些,下面就是實戰案例,一起來看一下。
實現跨域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(); });
这样就减少了多余的响应头。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是怎樣用Koa2框架的CORS完成跨域ajax請求的詳細內容。更多資訊請關注PHP中文網其他相關文章!