简介
为什么说是新一代?
新一代是koa,那上一代呢? 上一代就是大名鼎鼎的Express,koa经过了两个大版本,比起koa1,koa2使用了ES7进行开发,完全使用Promise并配合async
来实现异步。
本文编写的时候使用的是koa2
开始使用
简单使用
新建一个项目名叫myapp
,进入此目录
1 2 3 4
| npm init npm i koa -s touch index.js vim idnex.js
|
编写:
1 2 3 4 5 6
| var koa = require('koa'); var app = new koa(); app.use(function* () { this.body = 'Hello World'; }); app.listen(3000);
|
运行node index
,在浏览器输入http://localhost:3000/
即可看到输出Hello World。
使用脚手架
安装
1 2 3 4
| yarn global add koa-generator koa2 <my-app> cd <my-app> && npm i npm start
|
项目解析
路由管理
app.js
1 2 3 4 5 6 7
| const index = require('./routes/index') const users = require('./routes/users') app.use(index.routes(), index.allowedMethods()) app.use(users.routes(), users.allowedMethods())
|
routes/index
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const router = require('koa-router')() router.get('/', async (ctx, next) => { await ctx.render('index', { title: 'Hello Koa 2!' }) }) router.get('/string', async (ctx, next) => { ctx.body = 'koa2 string' }) router.get('/json', async (ctx, next) => { ctx.body = { title: 'koa2 json' } }) module.exports = router
|
routes/users
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const router = require('koa-router')() router.prefix('/users') router.get('/', function (ctx, next) { ctx.body = 'this is a users response!' }) router.get('/bar', function (ctx, next) { ctx.body = 'this is a users/bar response' }) module.exports = router
|
页面渲染
app.js
1 2 3 4 5
| router.get('/', async (ctx, next) => { await ctx.render('index', { title: 'Hello Koa 2!' }) })
|
- await是ES6的关键字,用于把异步代码同步化,就不再写回调函数了(callback)。
- ctx.render()函数,用于加载渲染引擎。
views/index.pug
1 2 3 4 5
| extends layout block content h1= title p Welcome to #{title}
|
日志分析
这是一个中间件,每一次的浏览行为,都会产生一条服务器日志,用于记录用户的访问情况。我们后台通过命令行启动后,后台的服务器就会一直存活着,接收浏览器的请求,同时产生日志。
1 2 3 4 5 6
| app.use(async (ctx, next) => { const start = new Date() await next() const ms = new Date() - start console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) })
|
最后要说的就是服务器日志了,每一次的浏览行为,都会产生一条服务器日志,用于记录用户的访问情况。我们后台通过命令行启动后,后台的服务器就会一直存活着,接收浏览器的请求,同时产生日志。
日志中,200表示正常访问,404是没有对应的URL错误。可以看到每次访问的路径都被记录了,包括后台的路径和css的文件路径,还包括了访问协议,响应时间,页面大小等。
我们可以自定义日志格式,记录更多的信息,也可以记录对自己有用的信息。这样我们就构建出一个最小化的web应用了。
中间件
koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()
来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| app.use(async (ctx, next) => { if (await checkUserPermission(ctx)) { await next(); } else { ctx.response.status = 403; } }); app.use(async (ctx, next) => { console.log(`${ctx.request.method} ${ctx.request.url}`); await next(); }); app.use(async (ctx, next) => { const start = new Date().getTime(); await next(); const ms = new Date().getTime() - start; console.log(`Time: ${ms}ms`); });
|
middleware的顺序很重要,也就是调用app.use()
的顺序决定了middleware的顺序。
此外,如果一个middleware没有调用await next()
,会怎么办?答案是后续的middleware将不再执行了。
比如上述代码中,检测用户权限,如果权限不足,则直接返回403,不再执行后续操作。
ctx
对象有一些简写的方法,例如ctx.url
相当于ctx.request.url
,ctx.type
相当于ctx.response.type
中间件开发
静态资源处理
static-files.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const path = require('path'); const mime = require('mime'); const fs = require('mz/fs'); function staticFiles(url, dir) { return async (ctx, next) => { let rpath = ctx.request.path; if (rpath.startsWith(url)) { let fp = path.join(dir, rpath.substring(url.length)); if (await fs.exists(fp)) { ctx.response.type = mime.getType(rpath); ctx.response.body = await fs.readFile(fp); } else { ctx.response.status = 404; } } else { await next(); } }; } module.exports = staticFiles;
|
注意到使用到了mime
,模块,用于解析mime类型,之前一直是使用mime.lookup
,使用的时候疯狂报错,查看了官方文档,才知道换成了mime.getType
,官方文档永远是最好的参考。 参考链接
app.js
1 2
| const staticFiles = require('./static-files'); app.use(staticFiles('/static/', __dirname + '/static'));
|
在浏览器输入http://localhost:3000/static/xxx
即可直接访问静态资源。
参考资料
廖雪峰: koa入门
粉丝日志: 新一代Node.js的Web开发框架Koa2
mime模块响应或设置Node.js的Content-Type头