一、歷史
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模式。