二、用koa庫進行web開發

一、歷史

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!

? ? ? ?

Try again

`;

? ? }

});

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模式。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容