浅谈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 相关文章推荐
prototype class详解
Sep 07 Javascript
javascript document.compatMode兼容性
Feb 23 Javascript
浅析Prototype的模板类 Template
Dec 07 Javascript
使用javascript做的一个随机点名程序
Feb 13 Javascript
jQuery中关于ScrollableGridPlugin.js(固定表头)插件的使用逐步解析
Jul 17 Javascript
javascript实现按回车键切换焦点
Feb 09 Javascript
jQuery支持动态参数将函数绑定到事件上的方法
Mar 17 Javascript
JavaScript数据类型判定的总结笔记
Jul 31 Javascript
Javascript中类式继承和原型式继承的实现方法和区别之处
Apr 25 Javascript
Vue中的ref作用详解(实现DOM的联动操作)
Aug 21 Javascript
Vue使用NProgress的操作过程解析
Oct 10 Javascript
微信小程序监听用户登录事件的实现方法
Nov 11 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
MOTOROLA 摩托罗拉 MODEL 66-XI五灯中波收音机
2021/03/02 无线电
PHP的范围解析操作符(::)的含义分析说明
2011/07/03 PHP
php多次include后导致全局变量global失效的解决方法
2015/02/28 PHP
PHP模板引擎Smarty之配置文件在模板变量中的使用方法示例
2016/04/11 PHP
PHP中session跨子域的三种实现方法
2016/07/25 PHP
Javascript 构造函数 实例分析
2008/11/26 Javascript
jQuery 使用手册(二)
2009/09/23 Javascript
event.currentTarget与event.target的区别介绍
2012/12/31 Javascript
用jQuery实现的智能隐藏、滑动效果的返回顶部代码
2014/03/18 Javascript
基于javascript实现简单的抽奖系统
2020/04/15 Javascript
Vue 固定头 固定列 点击表头可排序的表格组件
2016/11/25 Javascript
基于Node.js + WebSocket打造即时聊天程序嗨聊
2016/11/29 Javascript
基于BootStrap栅格栏系统完成网站底部版权信息区
2016/12/23 Javascript
JS组件系列之MVVM组件 vue 30分钟搞定前端增删改查
2017/04/28 Javascript
开发Vue树形组件的示例代码
2017/12/21 Javascript
JS实现电商商品展示放大镜特效
2020/01/07 Javascript
将Python代码打包为jar软件的简单方法
2015/08/04 Python
利用Python为iOS10生成图标和截屏
2016/09/24 Python
Pycharm技巧之代码跳转该如何回退
2017/07/16 Python
基于Python Numpy的数组array和矩阵matrix详解
2018/04/04 Python
python 获取指定文件夹下所有文件名称并写入列表的实例
2018/04/23 Python
python实现跨excel的工作表sheet之间的复制方法
2018/05/03 Python
Python OOP类中的几种函数或方法总结
2019/02/22 Python
pygame库实现俄罗斯方块小游戏
2019/10/29 Python
Python selenium 自动化脚本打包成一个exe文件(推荐)
2020/01/14 Python
任意一块网页内容实现“活”的背景(目前火狐浏览器专有)
2014/05/07 HTML / CSS
捷克电器和DJ设备网上商店:Electronic-star
2017/07/18 全球购物
英国第一独立滑雪板商店:The Snowboard Asylum
2020/01/16 全球购物
JBL加拿大官方商店:扬声器、耳机等
2020/10/23 全球购物
高中生学期学习自我评价
2014/02/24 职场文书
初中生操行评语大全
2014/04/24 职场文书
房展策划方案
2014/06/07 职场文书
介绍信的格式
2015/01/30 职场文书
爱鸟护鸟的宣传语
2015/07/13 职场文书
2015大一新生军训感言
2015/08/01 职场文书
MySQL七种JOIN类型小结
2021/10/24 MySQL