浅谈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 相关文章推荐
一个可以增加和删除行的table并可编辑表格中内容
Jun 16 Javascript
基于jQuery滑动杆实现购买日期选择效果
Sep 15 Javascript
Js的Array数组对象详解
Feb 22 Javascript
JS获取屏幕高度的简单实现代码
May 24 Javascript
jQuery基于ID调用指定iframe页面内的方法
Jul 06 Javascript
深入浅出ES6之let和const命令
Aug 25 Javascript
深入理解requestAnimationFrame的动画循环
Sep 20 Javascript
jquery easyui dataGrid动态改变排序字段名的方法
Mar 02 Javascript
javascript九宫格图片随机打乱位置的实现方法
Mar 15 Javascript
微信小程序之swiper轮播图中的图片自适应高度的方法
Apr 23 Javascript
Js中将Long转换成日期格式的实现方法
Jun 05 Javascript
js Array.slice的8种不同用法示例
Jul 10 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中实现生成静态文件的方法缓解服务器压力
2014/01/07 PHP
ThinkPHP使用心得分享-分页类Page的用法
2014/05/15 PHP
PHP+Ajax无刷新带进度条图片上传示例
2017/02/08 PHP
几行代码轻松实现PHP文件打包下载zip
2017/03/01 PHP
Yii2配置Nginx伪静态的方法
2017/05/05 PHP
PHP实现在windows下配置sendmail并通过mail()函数发送邮件的方法
2017/06/20 PHP
PHP使用ActiveMQ实例
2018/02/05 PHP
PHP定义字符串的四种方式详解
2018/02/06 PHP
ThinkPHP防止重复提交表单的方法实例分析
2018/05/10 PHP
bcastr2.0 通用的图片浏览器
2006/11/22 Javascript
Js 随机数产生6位数字
2010/05/13 Javascript
JavaScript的类型转换(字符转数字 数字转字符)
2010/08/30 Javascript
基于Jquery的标签智能验证实现代码
2010/12/27 Javascript
javascript中处理时间戳为日期格式的方法
2014/01/02 Javascript
可编辑下拉框的2种实现方式
2014/06/13 Javascript
Extjs Label的 fieldLabel和html属性值对齐的方法
2014/06/15 Javascript
基于jquery实现放大镜效果
2015/08/17 Javascript
js带前后翻页的图片切换效果代码分享
2015/09/08 Javascript
JS日期加减,日期运算代码
2015/11/05 Javascript
全面解析Bootstrap中scrollspy(滚动监听)的使用方法
2016/06/06 Javascript
vue组件间通信解析
2017/03/01 Javascript
微信小程序滚动Tab实现左右可滑动切换
2017/08/17 Javascript
koa router 多文件引入的方法示例
2019/05/22 Javascript
TypeScript魔法堂之枚举的超实用手册
2020/10/29 Javascript
python函数参数*args**kwargs用法实例
2013/12/04 Python
python 把文件中的每一行以数组的元素放入数组中的方法
2018/04/29 Python
Python实现求一个集合所有子集的示例
2018/05/04 Python
python模拟菜刀反弹shell绕过限制【推荐】
2019/06/25 Python
python爬虫之遍历单个域名
2019/11/20 Python
Kipling凯浦林美国官网:世界著名时尚休闲包袋品牌
2016/08/24 全球购物
美国第一香水网站:Perfume.com
2017/01/23 全球购物
Lancer Skincare官方网站:抗衰老皮肤护理
2020/11/20 全球购物
爱情保证书范文
2014/02/01 职场文书
支部书记四风问题自我剖析材料
2014/09/29 职场文书
学生检讨书怎么写?
2014/10/10 职场文书
2016高一新生军训心得体会
2016/01/11 职场文书