当前位置: 首页 > 后端技术 > Node.js

Koa2框架使用CORS来完成跨域ajax请求

时间:2023-04-03 15:09:04 Node.js

实现跨域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');awaitnext();});然后使用ajax发起一个简单的请求,比如post请求,就可以轻松的得到服务器的正确响应。实验代码如下:$.ajax({type:'post',url:'http://127.0.0.1:3001/async-post'}).done(data=>{console.log(data);})服务器端代码:router.post('/async-post',asyncctx=>{ctx.body={code:"1",msg:"succ"}});然后就可以得到正确的响应信息。这时候再看request和response的header信息,会发现requestheader里面有一个origin(还有一个referer就是请求的URL地址),还有一个Access-Control-Allow-Origin在响应标头中。现在可以发送简单请求,但发送非简单请求需要额外的配置。第一次发送非简单请求时,实际上会发送两个请求。第一个是预检请求。这个请求的请求方法是OPTIONS。该请求是否通过决定了此类非简单请求能否成功响应。为了能够在服务端匹配到这个OPTIONS类型的请求,需要做一个中间件来匹配并给出响应,这样pre-check才能通过。app.use(async(ctx,next)=>{if(ctx.method==='OPTIONS'){ctx.body='';}awaitnext();});这样OPTIONS请求就可以通过了。查看preflight请求的请求头,会发现多了两个请求头。Access-Control-Request-Method:PUTOrigin:http://localhost:3000通过这两个头信息与服务器协商,看是否满足服务器响应条件。很容易理解,既然requestheader中多了两条信息,那么responseheader自然也应该有两条对应的信息,如下:Access-Control-Allow-Origin:http://localhost:3000Access-Control-Allow-Methods:PUT,DELETE,POST,GET第一条消息和origin一样所以通过。第二条信息对应Access-Controll-Request-Method,如果请求的方法包含在服务器允许的响应方法中,那么这条也通过。两个约束都满足,所以可以成功发起请求。至此,相当于只完成了预检,真正的请求还没有发送。当然真正的请求也成功得到了响应,响应头如下(省略不重要的部分)Access-Control-Allow-Origin:http://localhost:3000Access-Control-Allow-Methods:PUT,DELETE,POST,GET请求header如下: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('访问控制-允许方法','PUT,DELETE,POST,GET');awaitnext();});至此我们已经完成了可以正确响应跨域ajax的基本配置,还有一些东西可以进一步配置。例如,到目前为止,每个非简单请求实际上会发送两个请求,一个预检和一个真实请求,这会损失性能。为了不发送预检请求,可以配置以下响应标头。Access-Control-Max-Age:86400这个responseheader的意思是设置一个相对时间,从非简单请求通过server端的检查开始,当经过的毫秒数小于Access-Control-Max-年龄到了,不需要做预校验,直接发送请求即可。当然简单请求是没有preflight的,所以这段代码对于简单请求是没有意义的。当前代码如下: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);等待下一个();});至此,可以响应跨域的ajax请求,但是请求头中不会携带本域下的cookies。如果想把cookies带到服务器端,让服务器端进一步设置cookies,还需要进一步配置。为了方便后续检测,我们在域名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);awaitnext();});这时候可以把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);}等待下一个();});您可以在不使用*通配符的情况下匹配多个来源。注意:ctx.origin不同于ctx.request.header.origin。ctx.origin是服务器的域名,ctx.request.header.origin是发送请求的请求头的来源。不要混淆两者。最后我们稍微调整一下自定义中间件的结构,避免每次请求返回Access-Control-Allow-Methods和Access-Control-Max-Age。这两个responseheaders其实不是每次都必须返回的。是的,只有在第一次有预检时返回。调整后顺序如下: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);}awaitnext();});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='';}awaitnext();});这样就减少了多少余的响应头。