二、用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模式。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容