koa是Express的下一代基于Node.js的web框架,目前有1.x和2.0两个版本。
历史
1. Express
Express是第一代最流行的web框架,它对Node.js的http进行了封装,用起来如下:
(index.js)
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function () {
console.log('Example app listening on http://127.0.0.1:3000!');
});
命令行結果:
Example app listening on http://127.0.0.1:3000!
web結果:
Hello World!
虽然Express的API很简单,但是它是基于ES5的语法,要实现异步代码,只有一个方法:回调。如果异步嵌套层次过多,代码写起来就非常难看:
(index1.js)
var express = require('express');
var app = express();
var fs = require('fs')
app.get('/test', function (req, res) {
fs.readFile('../index.html', function (err, data) {
if (err) {
res.status(500).send('read file1 error');
console.log(res.status(500))
}
fs.readFile('../index.html', function (err, data) {
if (err) {
res.status(500).send('read file2 error');
}
res.type('text/plain');
res.send(data);
});
});
});
app.listen(3000,function(){
console.log('Example app listening on port')
})
web結果:
index.html的源碼輸出,不識別標籤
2. koa 2.0
随着新版Node.js开始支持ES6,Express的团队又基于ES6的generator重新编写了下一代web框架koa。
创建koa2工程
首先,我们创建一个目录hello-koa并作为工程目录用VS Code打开。然后,我们创建app.js,输入以下代码:
// 导入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 = '<h1>Hello, koa2!</h1>';
});
// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');
web結果:
对于每一个http请求,koa将调用我们传入的异步函数来处理:
async (ctx, next) => {
await next();
// 设置response的Content-Type:
ctx.response.type = 'text/html';
// 设置response的内容:
ctx.response.body = '<h1>Hello, koa2!</h1>';
}
其中,参数ctx是由koa传入的封装了request和response的变量,我们可以通过它访问request和response,next是koa传入的将要处理的下一个异步函数。
上面的异步函数中,我们首先用await next();处理下一个异步函数,然后,设置response的Content-Type和内容。
由async标记的函数称为异步函数,在异步函数中,可以用await调用另一个异步函数,这两个关键字将在ES7中引入。
现在我们遇到第一个问题:koa这个包怎么装,app.js才能正常导入它?
:可以用npm命令直接安装koa。先打开命令提示符,务必把当前目录切换到hello-koa这个目录,然后执行命令:
C:\...\hello-koa> npm install koa@2.0.0
npm会把koa2以及koa2依赖的所有包全部安装到当前目录的node_modules目录下。
:在hello-koa这个目录下创建一个package.json,这个文件描述了我们的hello-koa工程会用到哪些包。完整的文件内容如下:
{
"name": "hello-koa2",
"version": "1.0.0",
"description": "Hello Koa 2 example with async",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"keywords": [
"koa",
"async"
],
"author": "Michael Liao",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/michaelliao/learn-javascript.git"
},
"dependencies": {
"koa": "2.0.0"
}
}
其中,dependencies描述了我们的工程依赖的包以及版本号。其他字段均用来描述项目信息,可任意填写。
然后,我们在hello-koa目录下执行npm install就可以把所需包以及依赖包一次性全部装好:
C:...\hello-koa> npm install
很显然,第二个方法更靠谱,因为我们只要在package.json正确设置了依赖,npm就会把所有用到的包都装好。
注意,任何时候都可以直接删除整个node_modules目录,因为用npm install命令可以完整地重新下载所有依赖。并且,这个目录不应该被放入版本控制中。
koa middleware
让我们再仔细看看koa的执行逻辑。核心代码是:
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
});
每收到一个http请求,koa就会调用通过app.use()注册的async函数,并传入ctx和next参数。
我们可以对ctx操作,并设置返回内容。但是为什么要调用await next()?
原因是koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能。
例如,可以用以下3个middleware组成处理链,依次打印日志,记录处理时间,输出HTML:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log(555555555)
console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL
await next(); // 调用下一个middleware
console.log(7777777)
});
app.use(async (ctx, next) => {
const start = new Date().getTime(); // 当前时间
console.log(111111111)
await next(); // 调用下一个middleware
console.log(222222222)
const ms = new Date().getTime() - start; // 耗费时间
console.log(`Time: ${ms}ms`); // 打印耗费时间
});
app.use(async (ctx, next) => {
console.log(333333333)
await next();
ctx.response.type = 'text/html';
ctx.response.body = '<h1>Hello, koa2!</h1>';
console.log(444444444)
});
app.listen(3000);
console.log('http://127.0.0.1:3000/666')
middleware的顺序很重要,也就是调用app.use()的顺序决定了middleware的顺序。
此外,如果一个middleware没有调用await next(),会怎么办?答案是后续的middleware将不再执行了。这种情况也很常见,例如,一个检测用户权限的middleware可以决定是否继续处理请求,还是直接返回403错误:
var Koa = require('koa')
var app = new Koa()
function checkUserPermission(){
console.log('wo')
}
app.use(async(ctx,next )=>{
if(await checkUserPermission(ctx)){
await next()
console.log(666)
}else{
ctx.response.status = 403;
console.log(777)
}
})
app.listen(3000)
console.log('http://127.0.0.1:3000')
理解了middleware,我们就已经会用koa了!
最后注意ctx对象有一些简写的方法,例如ctx.url相当于ctx.request.url,ctx.type相当于ctx.response.type。
小插曲:在安装之前,首先检查自己的所在目录的路径是否有中文字符,最好不要用中文字符,否则会有些莫名奇妙的报错,比如npm init生成package.json会有莫名的bug,或者安装插件的时候发错这样的error:
+ koa@2.0.0
added 38 packages from 20 contributors and audited 53 packages in 6.332s
found 1 high severity vulnerability
run `npm audit fix` to fix them, or `npm audit` for details
按顺序的输入npm audit fix
运行,在输入npm audit
运行,就能debug这个errer,但是如果路径上有中文路径可能就debug不了。