前言
- 我们把 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 的核心设计思路是为中间件层提供高级语法糖封装,以增强其互用性和健壮性,并使得编写中间件变得相当有趣
const Koa = require('koa');
const app = new Koa();
app.use(ctx => {
ctx.body = 'hello world'
});
app.listen(8080)
- 可以看得出,实际上 Koa 使用的其实就是 http 模块,只是把它封装了,而中间键可以帮我们做很多一系列的东西
中间键 middleware
- app.use() 是用于注册中间件并且必须是生成器函数
- Koa 的中间件通过一种更加传统的方式进行级联,摒弃了以往 node 频繁的回调函数造成的复杂代码逻辑
- koa会把很多 中间键函数 组成一个处理链,每个 中间键函数 都可以做一些自己的事情,然后用
next() 来调用下一个 中间键函数
- 中间键必须是一个函数,可为异步函数:通过es7中的async和await来处理
- use 内部封装了两个对象:
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'
});
app.use((ctx, next)=> {
console.log(4);
});
ctx(Koa Context):
- 他把 node 的
request 和 response 对象封装在一个单独的对象里面,其为编写 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 等方法应对不同的请求方式
- 每个方法包含两个参数:
- 注册的路由
- 回调函数
const Koa = require('koa');
const app = new Koa();
const KoaRouter = require('koa-router');
const router = new KoaRouter();
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]):
- dir: 需要映射的目录
- 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.dynamic 为 true ,他也为 true
options.files: 与 files 相同
- files: 可选单独文件的配置(这里最好使用
path.normalize 规范一下地址,否则有可能找不到对象)
const Koa = require('koa');
const app = new Koa();
const path = require('path');
const KoaStaticCache = require('koa-static-cache');
const files = {}
app.use(KoaStaticCache('./static', {
prefix: '/public',
gzip: true,
dynamic: true
}, files));
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) {
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);