一、历史
1、koa是express的下一代,是Express的团队基于ES6语法中的generator写的web框架
2、express 的劣势:它是基于ES5的语法,如果异步嵌套层次过多,代码写起来就非常难看
(1)这是用express的代码:
app.get('/test', function (req, res) {
fs.readFile('/file1', function (err, data) {
if (err) {
res.status(500).send('read file1 error');
}
fs.readFile('/file2', function (err, data) {
if (err) {
res.status(500).send('read file2 error');
}
res.type('text/plain');
res.send(data);
});
});
});
(2)使用koa:将读取文件的方法移出去
app.use('/test', function *() {
yield doReadFile1();
var data = yield doReadFile2();
this.body = data;
});
(3)为了简化异步代码,ES7(目前是草案,还没有发布)引入了新的关键字async和await,可以轻松地把一个function变为异步模式。koa团队非常超前地基于ES7开发了koa2,它完全使用Promise并配合async来实现异步:
app.use(async (ctx, next) => {
await next();
var data = await doReadFile();
ctx.response.type = 'text/plain';
ctx.response.body = data;
});
出于兼容性考虑,目前koa2仍支持generator的写法,但下一个版本将会去掉。
二、入门应用
1、源码:
// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');
// 创建一个Koa对象表示web app本身:
const app = new Koa();
// 对于任何请求,app将调用该异步函数处理请求:
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '
Hello, koa2!
';});
// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');
(1)ctx:封装了request和response的变量
(2)await调用另一个异步函数
2、导入koa2:编辑好package.json中的依赖项,然后执行npm install
3、启动方式:根据喜好选择
(1)使用终端:node app.js
(2)VSCode:编辑好launch.json,然后使用VSCode的调试工具
(3)npm:编辑package.json中的scripts,其中start对应的就是启动命令
4、middleware:koa中的关键概念
(1)上述代码中,每收到一个http请求,koa就会调用通过app.use()注册的async函数,并传入ctx和next参数
(2)koa可以把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能。例如,可以用以下3个middleware组成处理链,依次打印日志,记录处理时间,输出HTML:
app.use(async (ctx, next) => {
console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL
await next(); // 调用下一个middleware
});
app.use(async (ctx, next) => {
const start = new Date().getTime(); // 当前时间
await next(); // 调用下一个middleware
const ms = new Date().getTime() - start; // 耗费时间
console.log(`Time: ${ms}ms`); // 打印耗费时间
});
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '
Hello, koa2!
';});
(3)调用app.use()的顺序决定了middleware的顺序,如果一个middleware没有调用await next(),后续的middleware将不再执行了。通过这样的机制,我们可以控制程序执行的走向。如:
app.use(async (ctx, next) => {
if (await checkUserPermission(ctx)) {
await next();
} else {
ctx.response.status = 403;
}
});
三、处理URL
1、引入koa-router这个middleware,让它负责URL映射的各个处理方式。
(1)先用npm导入koa-router
(2)使用koa-router之前:
app.use(async (ctx, next) => {
if (ctx.request.path === '/test') {
ctx.response.body = 'TEST page';
} else {
await next();
}
});
(3)使用koa-router之后:
router.get('/hello/:name', async (ctx, next) => {
var name = ctx.params.name;
ctx.response.body = `
Hello, ${name}!
`;});
router.get('/', async (ctx, next) => {
ctx.response.body = '
Index
';});
// add router middleware:
app.use(router.routes());
相当于把URL映射交给了koa-router,最终把koa-router交给app,并省略了await语句,非常简洁。
2、koa-bodyparser用于解析原始request请求,然后,把解析后的参数,绑定到ctx.request.body中,这样我们也能处理post请求了。
const bodyParser = require('koa-bodyparser');
router.post('/signin', async (ctx, next) => {
var
name = ctx.request.body.name || '',
password = ctx.request.body.password || '';
console.log(`signin with name: ${name}, password: ${password}`);
if (name === 'koa' && password === '12345') {
ctx.response.body = `
Welcome, ${name}!
`;} else {
ctx.response.body = `
Login failed!
`;
}
});
3、有了处理URL的办法,我们可以把项目结构设计的更合理:
url2-koa/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- controllers/
| |
| +- login.js <-- 处理login相关URL
| |
| +- users.js <-- 处理用户管理相关URL
|
+- app.js <-- 使用koa的js
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包
(1)把不同URL映射任务分别组织到不同的moudles中,把他们放在controllers目录下面
var fn_hello = async (ctx, next) => {
var name = ctx.params.name;
ctx.response.body = `
Hello, ${name}!
`;};
module.exports = {
'GET /hello/:name': fn_hello
};
(2)添加controller.js模块,让它自动扫描controllers目录,找到所有js文件,导入,然后注册每个URL
function addMapping(router, mapping) {//根据映射模块执行映射任务
for (var url in mapping) {
if (url.startsWith('GET ')) {
var path = url.substring(4);
router.get(path, mapping[url]);
console.log(`register URL mapping: GET ${path}`);
} else if (url.startsWith('POST ')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
} else {
console.log(`invalid URL: ${url}`);
}
}
}
function addControllers(router) { //导入映射模块
var files = fs.readdirSync(__dirname + '/controllers');
var js_files = files.filter((f) => {
return f.endsWith('.js');
});
for (var f of js_files) {
console.log(`process controller: ${f}...`);
let mapping = require(__dirname + '/controllers/' + f);
addMapping(router, mapping);
}
}
module.exports = function (dir) {
let
controllers_dir = dir || 'controllers', // 如果不传参数,扫描目录默认为'controllers'
router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
};
(3)简化app.js,将来app.js就不会与‘URL映射任务’耦合了
// 导入controller middleware:
const controller = require('./controller');
// 使用middleware:
app.use(controller());
四、Nunjucks:一个模板引擎,说白了就是拼字符串,http://mozilla.github.io/nunjucks/,这部分不深入学习
1、一般的应用,就是拼接一个html,返回给客户端。
2、结合面向对象、URL处理,我们很容易想到MVC模式。