浅谈Koa2框架利用CORS完成跨域ajax请求


Posted in Javascript onMarch 06, 2018

实现跨域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();
});

这样就减少了多余的响应头。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS实现self的resend
Jul 22 Javascript
用JSON做数据传输格式中的一些问题总结
Dec 21 Javascript
灵活应用js调试技巧解决样式问题的步骤分享
Mar 15 Javascript
js鼠标点击事件在各个浏览器中的写法及Event对象属性介绍
Jan 24 Javascript
js动态移动滚动条至底部示例代码
Apr 24 Javascript
js随机生成字母数字组合的字符串 随机动画数字
Sep 02 Javascript
正则表达式优化JSON字符串的技巧
Dec 24 Javascript
基于javascript的异步编程实例详解
Apr 10 Javascript
bootstrap下拉框动态赋值方法
Aug 10 Javascript
JavaScript偏函数与柯里化实例详解
Mar 27 Javascript
vue keep-alive 动态删除组件缓存的例子
Nov 04 Javascript
如何用threejs实现实时多边形折射
May 07 Javascript
JavaScript基础心法 深浅拷贝(浅拷贝和深拷贝)
Mar 05 #Javascript
JavaScript基础心法 数据类型
Mar 05 #Javascript
js获取html页面代码中图片地址的实现代码
Mar 05 #Javascript
vue axios 在页面切换时中断请求方法 ajax
Mar 05 #Javascript
node.js通过axios实现网络请求的方法
Mar 05 #Javascript
axios发送post请求springMVC接收不到参数的解决方法
Mar 05 #Javascript
基于vue 添加axios组件,解决post传参数为null的问题
Mar 05 #Javascript
You might like
如何使用PHP获取指定日期所在月的开始日期与结束日期
2013/08/01 PHP
php数据库操作model类(使用__call方法)
2016/11/16 PHP
游戏人文件夹程序 ver 3.0
2006/07/14 Javascript
javaScript 关闭浏览器 (不弹出提示框)
2010/01/31 Javascript
jQuery.Validate验证库的使用介绍
2013/04/26 Javascript
jquery 清空file域示例(兼容个浏览器)
2013/10/11 Javascript
在firefox和Chrome下关闭浏览器窗口无效的解决方法
2014/01/16 Javascript
用js闭包的方法实现多点标注冒泡示例
2014/05/29 Javascript
Egret引擎开发指南之创建项目
2014/09/03 Javascript
深入讲解AngularJS中的自定义指令的使用
2015/06/18 Javascript
前端JS面试中常见的算法问题总结
2016/12/23 Javascript
提高Web性能的前端优化技巧总结
2017/02/27 Javascript
jQuery接受后台传递的List的实例详解
2017/08/02 jQuery
详解webpack中的hash、chunkhash、contenthash区别
2018/01/05 Javascript
在 Angular6 中使用 HTTP 请求服务端数据的步骤详解
2018/08/06 Javascript
pymongo为mongodb数据库添加索引的方法
2015/05/11 Python
python xml解析实例详解
2016/11/14 Python
django数据库migrate失败的解决方法解析
2018/02/08 Python
Django Web开发中django-debug-toolbar的配置以及使用
2018/05/06 Python
解决Django中多条件查询的问题
2019/07/18 Python
利用 Flask 动态展示 Pyecharts 图表数据方法小结
2019/09/04 Python
python 多维高斯分布数据生成方式
2019/12/09 Python
Python图像处理库PIL的ImageEnhance模块使用介绍
2020/02/26 Python
python3 循环读取excel文件并写入json操作
2020/07/14 Python
吉力贝官方网站:Jelly Belly
2019/03/11 全球购物
优秀演讲稿范文
2013/12/29 职场文书
《放小鸟》教学反思
2014/04/20 职场文书
英语故事演讲稿
2014/04/29 职场文书
责任书格式范文
2014/07/28 职场文书
县政府领导班子“四风”方面突出问题整改措施
2014/09/23 职场文书
公安机关纪律作风整顿个人剖析材料材料
2014/10/10 职场文书
2015年五四青年节演讲稿
2015/03/18 职场文书
公司放假通知怎么写
2015/04/15 职场文书
祝酒词范文
2015/08/12 职场文书
分家协议书范本
2016/03/22 职场文书
基于Python编写一个监控CPU的应用系统
2022/06/25 Python