Koa

1,307 阅读6分钟

前言

  • 我们把 http、fs 等模块直接使用搭建一个简易服务器问题不大,但是当业务逻辑越来越麻烦的时候,我们自己写的代码就会越来越多,越来越麻烦,重复性也高
  • 这个时候我们使用框架就会非常舒服,框架并不会为我们增加功能,他只是把我们的业务按照某种规则封装整理提供给我们,让我们轻松使用、方便
  • Node 端 webserver 用的较多的主流框架有:express、Koa等
    • express 内置了很多中间件,集成度高,使用省心
    • koa 轻量简洁,容易定制
  • 下面我们就来学习一下 Koa

简介

  • koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手
  • webserver 的主要工作就是 解析请求 -> 处理响应,Koa 把这两样东西的基本逻辑都封装了,还提供了一些接口进行扩展

安装

  • Koa需要 node v7.6.0或更高版本来支持ES2015、异步方法
npm install koa

使用 Koa 实现第一个服务器(简单使用)

  • Koa 应用是一个包含一系列中间件 generator 函数的对象。 这些中间件函数基于 request 请求以一个类似于栈的结构组成并依次执行
  • Koa 的核心设计思路是为中间件层提供高级语法糖封装,以增强其互用性和健壮性,并使得编写中间件变得相当有趣
// 引用 Koa 框架
const Koa = require('koa');
// 实例一个 app 对象
const app = new Koa();
// koa 的中间键 使用 use 语法
app.use(ctx => {
    // ctx.body 相当于 http.createServer((req, res) => { res.end('hello world') });
    ctx.body = 'hello world'
});
// 相当于 http.createServer().listen
app.listen(8080)
  • 可以看得出,实际上 Koa 使用的其实就是 http 模块,只是把它封装了,而中间键可以帮我们做很多一系列的东西

中间键 middleware

  • app.use() 是用于注册中间件并且必须是生成器函数
  • Koa 的中间件通过一种更加传统的方式进行级联,摒弃了以往 node 频繁的回调函数造成的复杂代码逻辑
  • koa会把很多 中间键函数 组成一个处理链,每个 中间键函数 都可以做一些自己的事情,然后用 next() 来调用下一个 中间键函数
  • 中间键必须是一个函数,可为异步函数:通过es7中的async和await来处理
  • use 内部封装了两个对象:
    • ctx
    • next

next

  • next 是 koa 传入的将要处理的下一个函数
  • 如果不调用 next 则不会执行下一步函数
  • 你可以把他想象为 Vue 中的 路由守卫
  • 当执行到 yield next 语句时,Koa 会暂停了该中间件,继续执行下一个符合请求的中间件,然后控制权再逐级返回给上层中间件
app.use((ctx, next)=> {
    next();
    console.log(1);
});
app.use((ctx, next)=> {
    console.log(2);
    next();
});
app.use((ctx, next)=> {
    console.log(3);
    ctx.body = 'hello world'
});
// 上一个 use 没有调用 next() 不被执行
app.use((ctx, next)=> {
    console.log(4);
});

// 2 -> 3 -> 1

ctx(Koa Context):

  • 他把 node 的 requestresponse 对象封装在一个单独的对象里面,其为编写 web 应用和 API 提供了很多有用的方法
  • ctx 对象包含:
    • req - Node 的 request 对象
    • res - Node 的 response 对象
    • request - Koa 的 Request 对象
    • response - Koa 的 Response 对象
  • ctx.body
    • 返回正文
    • 与 ctx.response.body 相等
  • ctx.status:设置状态码
  • ctx.url:获取请求 URL
  • ctx.header:头信息
  • ......

koa-router

  • 在实际应用中,我们不可能只有一个接口,这个时候需要有路由管理,我们可以借助 Koa-Router 轻松处理路由
  • 路由就是把一个 URL 与 一个函数 进行关联管理
  • koa-router 做的其实就是匹配 URL,然后执行我们注册的对应函数

安装

npm install koa-router

new KoaRouter()

  • koa-router 的实例对象
  • 对象内包含了 get、post 等方法应对不同的请求方式
  • 每个方法包含两个参数:
    1. 注册的路由
    2. 回调函数
      • 这个函数也相当于一个中间键
const Koa = require('koa');
const app = new Koa();
// 引入 koa-router
const KoaRouter = require('koa-router');
// 实例化一个 路由对象
const router = new KoaRouter();

// 等同于 Node 判断 req.url === '/'
router.get('/', ctx => {
    ctx.body = 'hello world'
});
router.get('/test', ctx => {
    ctx.body = 'test'
});

router.routes()

  • router.routes 会返回一个中间键函数
  • 这个中间键函数会分析 URL,把分析到的 URL 与 注册的 URL 进行匹配,匹配成功的就执行对应的注册函数
const routerMiddleware = router.routes();
app.use(routerMiddleware);

app.listen(8080);
  • new KoaRouter() + router.routes() 为一个完整示例

koa-static-cache

  • npm install koa-static-cache 安装
  • 他是一个专门处理静态资源的中间键
  • 他能将我们的静态资源映射出来
  • staticCache有三个参数(dir[, options][, files]):
    1. dir: 需要映射的目录
    2. options: 可选配置项
      • options.dir: 映射目录(优先级低于 dir
      • options.maxAge: 文件的缓存控制最大期限(默认为0)
      • options.cacheControl: 可选缓存控制标头(会覆盖 options.maxAge
      • options.buffer: (bool) 将文件存储在内存中,而不是在每次请求时从文件系统中流式传输
      • options.gzip: (bool) 当请求的 accept-encoding 包含gzip时,文件将被gzip压缩
      • options.usePrecompiledGzip: (bool) 尝试使用从磁盘加载的gzip文件
      • options.alias: 别名的对象映射(obj: 可使文件拥有别名)
      • options.prefix: URL 前缀替换
      • options.dynamic: (bool) 动态加载文件(为 true 时,目录中文件新增、删除会被监听)
      • options.filter: 过滤目录中的文件(如果设置了数组-仅允许列出文件)
      • options.preload: (bool) 是否在初始化时缓存资产,默认为true,如options.dynamictrue ,他也为 true
      • options.files: 与 files 相同
    3. files: 可选单独文件的配置(这里最好使用 path.normalize 规范一下地址,否则有可能找不到对象)
const Koa = require('koa');
const app = new Koa();
const path = require('path');
// 引用 koa-static-cache
const KoaStaticCache = require('koa-static-cache');

const files = {}

// 请求会映射 ./static 目录下的文件
app.use(KoaStaticCache('./static', {
    // 设置 URL 前缀 /css.css => /public/css.css 
    prefix: '/public',
    gzip: true,
    dynamic: true
}, files));

// 如需映射多个目录,不必再使用 use
KoaStaticCache('/public', {})

// 单独设置文件处理
files[path.normalize('/public/css.css')].maxAge = 365 * 24 * 60 * 60;

app.listen(8080);

自己封装 Koa 中间键

  • 中间键必须是一个函数
  • 通过 use 使用
  • 可以 CommonJS 规范使得逻辑易读性更高(模块化)
  • 下面例子简单模仿了 koa-static-cache 的一点映射功能
const Koa = require('koa');
const app = new Koa();
const fs = require('fs');
const path = require('path');

function myStatic(dir) {
    // 返回一个中间键函数 为了传递 dir
    return (ctx, next) => {
        // 读取目录下文件
        const files = fs.readdirSync(dir);
        // 判断请求是否是目录下的文件(判断比较粗糙,目的明白怎么封装中间键)
        if (files.map(item => '/' + item).indexOf(ctx.url) > -1) {
            // 头信息需要按照需求写,我这边就偷懒了
            ctx.set('Content-Type', 'text/plain; charset=utf-8');
            ctx.body = fs.readFileSync(path.join(dir + ctx.url));
        } else {
            ctx.body = "Not Find";
        }
    }
}

app.use(myStatic('./static'));

app.listen(8080);