Koa - Node.js框架学习@郝晨光


本文由郝晨光整理总结并编写,未经允许禁止转载。

前言

学习koa,我之前学习过express,但是在使用express的时候,还是一直使用的回调函数的方式来处理异步,现在想想真是恐怖,后来了解到koa这个框架,它相对于express来说,小巧了很多,对于异步的处理也变得更加优雅了。用官方的话来说,koa是基于Node.js平台的下一代web平台开发框架。


Image 3.png

正文

  1. 首先肯定要先开始一个新的项目,新建一个文件夹

  2. 在文件夹目录中,使用命令提示符执行npm init -y,初始化package.json文件;

  3. 安装

    • Koa 依赖 node - v7.6.0 或 ES2015及更高版本和 async 方法支持。
    • 先查看node的版本 node -v;
    • 然后执行npm install koa 进行安装;
  4. 使用

    • 新建index.js;
    • 先上官网上最经典的hello wrold案例;
    • const Koa = require('koa');
      const app = new Koa();
      app.use(async ctx => {
          ctx.body = 'hello word!'
      })
      app.listen(8000, () => {
          console.log('Koa server listen in http://localhost:8000/');
      })
      
    • 打开浏览器,输入localhost:8000/,就可以看到我们的页面上显示了hello world!;
    • 那我们的第一个koa应用就已经运行起来了~。
  5. 读取 HTML 页面

    • 在Koa中,我们如果要使用 HTML 应该怎么做呢?
    • 在原生nodejs或者express中,我们使用的是fs模块,然后使用回调函数,一层套一层;
    • 在Koa中,我们依旧使用fs模块,但是,要对fs模块的读取文件,进行进一步的封装;
    • 在目录下,我们新建一个 index.html,并随便写一点东西;
    • 接着开始我们渐行渐远的程序生涯;
    • const Koa = require('koa');
      const fs = require('fs');
      const app = new Koa();
      function readFile(url) {
        return new Promise((resolve, reject) => {
            fs.readFile(url, (err,file) => {
                if(err) {
                    reject(err);
                }else {
                    resolve(file);
                }
            })
        })
      }
      app.use(async ctx => {
          ctx.response.type = 'html'; // 重点
          ctx.body = await readFile('index.html');
      })
      app.listen(8000, () => {
          console.log('Koa server listen in http://localhost:8000/');
      })
      // 本文由郝晨光整理总结并编写,未经允许禁止转载。
      
    • 可以看到,我们利用Promise将fs模块的readFile方法进行了二次封装,读取了本地的index.html文件
    • 重点的地方我已经标出来了,为什么要标重点呢?
    • 因为在Koa中,ctx.body默认返回的类型是text/plain;而我们要返回html文件,所以应该在返回文件之前,设置好返回类型,让客户端可以正确的接收;
    • 然后我们使用async + await组合,在数据读取完成之后,返回读取的文件;
    • 最后,打开浏览器,可以看到浏览器上输出的就是我们index.html文件中写的内容
  6. 读取静态资源

    • html文件我们已经可以正确的读取并返回了,在我们的页面上,避免不了会使用很多的静态资源,例如css、js、image、font等等;这些静态资源我们应该怎么处理呢?
    • 在这里,我们使用一个Koa的中间件koa-static
    • 在命令提示符中执行npm install koa-static
    • 接着书写我们的程序
    • // ~~ 省略 ~~
      const app = new Koa(); // 新建koa对象
      const KoaStatic = require('koa-static'); // koa静态文件读取
      app.use(KoaStatic(__dirname)); // 使用中间件
      // ~~ 省略 ~~
      
      • 接着,打开我们的浏览器,可以看到静态资源已经可以正确的请求到了;
      • 当然了,这个时候,我们可以把之前返回html文件时设置响应类型的一步省略掉了,因为koa-static已经帮我们做了这件事了。
  7. 定义路由

    • 所谓的路由,就是根据不同的请求路径,响应不同的内容;
    • 在Koa中,我们可以定义原生路由,也可以使用封装好的路由中间件;
    • 先看一下原生路由的写法吧!
    • app.use(async ctx => {
        if(ctx.request.method==='GET') { // GET请求
            switch (ctx.request.path) {
                case '/': // 匹配默认路由
                    ctx.body = await readFile('index.html'); // 返回index.html文件
                    break;
                case '/about': // 匹配/about路由
                    ctx.body = 'about路由页面'; // 返回about路由页面
                    break;
                default:
                    ctx.body = await readFile('index.html'); // 默认返回index.html文件
                    break;
            }
        }else if(ctx.request.method==='POST') { // POST请求
            switch (ctx.request.path) {
                default:
                    ctx.body = 'post 请求'; // 响应post请求
                    break;
            }
        }
      });
      //本文由郝晨光整理总结并编写,未经允许禁止转载。
      
    • 可以看到我上边写的,对请求方式以及路由进行了处理,在不同的请求方式,不同的请求路径下,执行不同的操作,响应不同的结果。


      原生路由
  8. 路由中间件

    • 我们使用原生方式定义路由,未免有些太过繁琐,并且代码不便于阅览和维护;所以,建议使用中间件的方式进行路由配置。
    • 我这里使用的是 koa-router 这个中间件,当然,koa的路由中间件不是只有这一种。
    • 在命令提示符中安装 npm install koa-router;
    • 接着,进行我们的程序生涯;
    • // ~~ 省略 ~~
      const KoaRouter = require('koa-router');
      const router = new KoaRouter();
      router.get('/',async ctx => {
          ctx.body = await readFile('index.html');
      });
      
      router.get('/about',async ctx=> {
           ctx.body = 'about路由页面'
      });
      
      router.post('/form',async ctx => {
          ctx.body = 'post请求'
      });
      
      app.use(router.routes());
      // ~~ 省略 ~~
      
    • 打开页面,并进行路由切换,可以看到没有任何问题;
    • 但是这样所有的路由配置都写在了index.js中,不利于维护,所以我们要将路由内容和公用方法提取出来。


      路由中间件
  1. 代码模块化

    • 首先对路由进行模块化
    • 在目录下新建routes文件夹,并新建home.js,用来存放关于首页的一些路由配置。
    • 将上边的路由代码单独写到home.js中,并抛出,如下:
    • home.js
    • const KoaRouter = require('koa-router');
      const router = new KoaRouter();
      
      router.get('/',async ctx => {
        ctx.body = await readFile('index.html');
      });
      
      router.get('/about',async ctx=> {
        ctx.body = 'about 路由'
      });
      
      router.post('/from',async ctx => {
         ctx.body = 'post请求'
      });
      
      module.exports = router;
      
    • index.js
    • const homeRouter = require('./routes/home');
      
      app.use(KoaStatic(__dirname));
      
      app.use(homeRouter.routes());
      
    • 在index.js中,直接引入使用即可,和原有代码没有任何区别。
    • 需要注意的是,对于功能中间件,例如koa-static这种,我们应该放在路由中间件之前。
    • 否则的话可能会出错,在路由中不能正确的读取静态文件等。
  2. 二级路由

    • 已经学会了一级路由的定义,我们趁热打铁,学习二级路由
    • 在routes目录下,新建user.js,定义用户路由
    • // user.js
      const KoaRouter = require('koa-router');
      const router = new KoaRouter({
        prefix: '/user'
      });
      
      router.get('/',async ctx => {
        ctx.body = '用户界面'
      });
      
      router.get('/:id',async ctx => {
        ctx.body = '用户详情'+ctx.params.id;
      });
      
      router.post('/login',async ctx => {
        ctx.body = '用户注册成功!'
      });
      
      module.exports = router;
      
      // index.js
      // ~~ 省略 ~~
      const userRouter = require('./routes/user');
      // ~~ 省略 ~~
      app.use(userRouter.routes());
      
    • 接着,打开浏览器,我们测试一下我们的二级路由,是没有任何问题的。


      二级路由.gif
  3. post请求处理

    • 我们都知道,使用post请求一般用来提交表单,或者修改数据等。而post请求中的数据,存放在请求体中,使用原生Node的方法的话,我们需要监听原生req对象上的data方法以及end方法,并将数据格式转化。而express中,我们有一个body-parser的插件可以使用。
    • 那么在Koa中,我们应该使用什么呢?
    • 在Koa中,我们使用 koa-bodyparser 这个中间件来处理post请求的数据
    • 在命令提示符安装 npm install koa-bodyparser;
    • 接着我们在index.js中使用这个中间件;
    • 加入下面两行代码,与原先使用koa-static中间件位置一样即可;
    • const KoaBodyParser = require('koa-bodyparser');
      
      app.use(KoaBodyParser());
      
    • 使用了这个中间件,我们就可以在ctx.request.body中拿到post请求的数据了;
    •  router.post('/login',async ctx => {
          ctx.body = ctx.request.body;
       });
      
    • 接着改造一下我们的html文件,提交一下表单;
    •   <form action="/user/login" method="post">
            姓名:<input type="text" name="name" autocomplete="off"/>
            <br>
            <input type="radio" name="sex" value="男"/>男
            <input type="radio" name="sex" value="女"/>女
            <input type="submit" value="提交form">
        </form>
      
      • 可以看到,表单提交正常,我们也正确的拿到了表单提交的数据。


        Koa提交表单
  4. CORS跨域配置

    • 在我们现在的服务器生涯中,避免不了要使用跨域,特别是CORS跨域。
    • 那么,在Koa中,我们应该怎么去设置跨域呢?
    • 我们可以使用中间件,也可以使用原生方法手写。
    • 老规矩,先看原生方法。
    •   app.use(async (ctx, next) => {
            // 允许来自所有域名请求
            ctx.set("Access-Control-Allow-Origin", "*");
            // 这样就能只允许 http://localhost:8080 这个域名的请求了
            // ctx.set("Access-Control-Allow-Origin", "http://localhost:8080");
      
            // 设置所允许的HTTP请求方法
            ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");
      
            // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段.
            ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
      
            // 服务器收到请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
      
            // Content-Type表示具体请求中的媒体类型信息
            ctx.set("Content-Type", "application/json;charset=utf-8");
      
            // 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。
            // 当设置成允许请求携带cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*";
            ctx.set("Access-Control-Allow-Credentials", true);
      
            // 该字段可选,用来指定本次预检请求的有效期,单位为秒。
            // 当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证
            // 下面的的设置只本次验证的有效时间,即在该时间段内服务端可以不用进行验证
            ctx.set("Access-Control-Max-Age", 300);
      
            /*
            CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
            Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
            */
            // 需要获取其他字段时,使用Access-Control-Expose-Headers,
            // getResponseHeader('myData')可以返回我们所需的值
            ctx.set("Access-Control-Expose-Headers", "myData");
            await next();
        })
      
      • 原生方法来自:node.js 应答跨域请求实现(以koa2-cors为例)
      • 接着我们来看中间件吧!
      • 在Koa2中,我们使用koa2-cors这个中间件来设置CORS跨域请求。
      • 先安装 npm install koa2-cors;
      • 接着,在index.js中使用。
      • const KoaCors = require('koa2-cors');
        // ~~ 省略 ~~
        // CORS跨域
        app.use(KoaCors({
            origin: ctx => {
                return ctx.request.header.origin;
            },
            exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
            maxAge: 5,
            credentials: true,
            allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
            allowHeaders: ['Content-Type', 'Authorization', 'Accept', 'Origin'],
        }));
        //本文由郝晨光整理总结并编写,未经允许禁止转载。
        
      • 特别需要注意的是,在app.use()使用koa2-cors的时候,我们应该把它放在别的中间件的前边,特别是静态资源处理和路由处理中间件的前边,这样可以保证我们在跨域请求静态资源的时候不会出问题。



如果本文对您有帮助,可以看看本人的其他文章:
前端常见面试题(十三)@郝晨光
前端常见面试题(十二)@郝晨光
前端常见面试题(十一)@郝晨光

结言
感谢您的查阅,本文由郝晨光整理并总结,代码冗余或者有错误的地方望不吝赐教;菜鸟一枚,请多关照
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容