刺探Koa和创设路由实例

2020-04-16 作者:网站首页   |   浏览(189)

时间: 2019-12-22阅读: 61标签: KoaKoa 介绍

一、历史

koa是由Express原班人马打造的,一个基于Node.js平台的 Web 开发框架

1、koa是express的下一代,是Express的团队基于ES6语法中的generator写的web框架

Express是基于 ES5 的语法,随着新版 Node.js 开始支持 ES6 ,该团队重新编写诞生了koa 1.0,而koa2.0则是更是超前的基于 ES7

2、express 的劣势:它是基于ES5的语法,如果异步嵌套层次过多,代码写起来就非常难看

安装 koa2

(1)这是用express的代码:

npm i koa

app.get('/test', function (req, res) {

创建 koa2 工程

    fs.readFile('/file1', function (err, data) {

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:const Koa = require('koa');// 创建一个Koa对象表示web app本身:const app = new Koa();app.use( ... )// 在端口3000监听:app.listen(3000);

        if (err) {

app.use([path,] function [, function…])

            res.status(500).send('read file1 error');

在 path 上安装中间件,每当请求的路径的基路径和该path匹配时,都会导致该中间件函数被执行,如果 path 没有被设定,那么默认为/

        }

app.use('/admin', function(req, res) { console.log(req.originalUrl); // '/admin/new'console.log(req.baseUrl); // '/admin'console.log(req.path); // '/new'});

app.use(async (ctx, next) = { await next(); // 处理下一个异步函数 ctx.response.type = 'text/html' // 设置 response 的 Content-Type ctx.response.body = 'h1Hello, koa2!/h1' // 设置 response 的内容})

        fs.readFile('/file2', function (err, data) {

由async标记的函数称为异步函数,在异步函数中,可以用await调用另一个异步函数,这两个关键字将在 ES7 中引入(用法看上去类似 Promise)

            if (err) {

上述代码中,参数ctx是由 koa 传入的封装了request和response的变量,我们可以通过它访问request和response,next是 koa 传入的将要处理的下一个异步函数

                res.status(500).send('read file2 error');

对于每一个 http 请求, koa 将调用通过app.use()注册的async函数,并传入ctx和next参数

            }

实例:

            res.type('text/plain');

const Koa=require('koa');const app=new Koa();app.use(async (ctx, next) = { console.log('第一1'); await next(); // 调用下一个middleware console.log('第一2');});app.use(async (ctx, next) = { console.log('第二1'); await next(); // 调用下一个middleware console.log('第二2');});app.use(async (ctx, next) = { console.log('第三1'); await next(); console.log('第三2');});app.listen(3000);// 打印结果如下:// 第一1// 第二1// 第三1// 第三2// 第二2// 第一2

            res.send(data);

koa-router 和 koa-bodyparser

        });

koa-router负责处理 URL 映射,通过它可以更便捷的对 URL 进行操作

    });

koa-router 使用

});

const router = require('koa-router')(); // 引入 koa-router……// add router middleware:app.use(router.routes());

(2)使用koa:将读取文件的方法移出去

注意导入koa-router的语句最后的()是对函数调用

app.use('/test', function *() {

通过使用 koa-router ,我们可以处理一些更复杂的 URL 操作,比如 GET 请求

    yield doReadFile1();

// log request URL:app.use(async (ctx, next) = { console.log(`h1Hello, ${name}!/h1`); await next();});// add url-route:router.get('/hello/:name', async (ctx, next) = { var name = ctx.params.name; // 通过 ctx.params.name 可以获取 URL 带来的变量 ctx.response.body = `h1Hello, ${name}!/h1`;});

    var data = yield doReadFile2();

处理 get 请求

    this.body = data;

用router.get('/path', async fn)处理的是 get 请求

});

处理 post 请求

(3)为了简化异步代码,ES7(目前是草案,还没有发布)引入了新的关键字async和await,可以轻松地把一个function变为异步模式。koa团队非常超前地基于ES7开发了koa2,它完全使用Promise并配合async来实现异步:

用router.post('/path', async fn)处理 post 请求

app.use(async (ctx, next) => {

需要注意,post 请求的数据,无法正确为客户端解析成正确的 request 的 body ,此时需要利用koa-bodyparser组件实现解析

    await next();

koa-bodyparser 使用

    var data = await doReadFile();

const router = require('koa-router')(); // 引入 koa-routerconst bodyParser = require('koa-bodyparser');app.use(bodyParser()); // 在 router.routes 之前注入 bodyParser……router.post('/path', async fn)……// add router middleware:app.use(router.routes());app.listen(3000);

    ctx.response.type = 'text/plain';

注意koa-bodyparser必须在router之前被注册到app对象上

    ctx.response.body = data;

路由实例提取路由

});

为了使目录结构更为美观,可以将所有的 URL 处理函数都提取出来

出于兼容性考虑,目前koa2仍支持generator的写法,但下一个版本将会去掉。

// index.jsvar fn_index = async (ctx, next) = { …… };var fn_signin = async (ctx, next) = { …… };module.exports = { 'GET /': fn_index, 'POST /signin': fn_signin};

二、入门应用

app.js页面在获取上,廖雪峰的实例代码略显复杂,如下

1、源码:

var fs = require('fs') // 引入 Node.js 核心模块 fs// 通过 fs.readdirSync 同步读取 /controllers 目录下的文件// 该源实例 index.js 存放于 /controllers 目录下var files = fs.readdirSync(__dirname   '/controllers');// 源实例注解:这里可以用sync是因为启动时只运行一次,不存在性能问题// 过滤该目录下所有 .js 后缀的文件var js_files = files.filter((f)={ return f.endsWith('.js');});// 循环所有 .js 后缀的文件for (var f of js_files) { // 动态引入每一个 js 文件,此处并非真实引入 let mapping = require(__dirname   '/controllers/'   f); for (var url in mapping) { // 此时的 mapping 为每一个 .js 文件的 exports 对象,循环该对象 if (url.startsWith('GET ')) { // 如果对象键名 url 已 'GET ' 开头 var path = url.substring(4); // 提取键名中的路径部分,此处是 '/' router.get(path, mapping[url]); } else if (url.startsWith('POST ')) { // 如果对象键名 url 已 'POST ' 开头 var path = url.substring(5); // 提取键名中的路径部分,此处是 '/signin' router.post(path, mapping[url]); } else { // 无效的URL: console.log(`invalid URL: ${url}`); } }}

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:

fs.readdirSync(path[, options])同步版本的 fs.readdir() ,参数如下pathString | Buffer | URLoptionsString | ObjectencodingString默认值'utf8'withFileTypesBoolean默认值false返回 string[] | Buffer[]| fs.Dirent[]

const Koa = require('koa');

可选的options参数可以是指定编码的字符串,也可以是具有encoding属性的对象,该属性指定用于传给回调的文件名的字符编码。 如果encoding设置为'buffer',则返回的文件名是Buffer对象。

// 创建一个Koa对象表示web app本身:

__dirname

const app = new Koa();

在每个 Node.js 模块中,除 require 、exports 等模块之外还都包含两个特殊成员

// 对于任何请求,app将调用该异步函数处理请求:

__dirname动态获取当前文件模块所属目录的绝对路径__filename动态获取当前文件的绝对路径

app.use(async (ctx, next) => {

str.startsWith(searchString [, position])

    await next();

startsWith()方法用来判断当前字符串是否是以另外一个给定的子字符串开头的,根据判断结果返回 true 或 false

    ctx.response.type = 'text/html';

searchString要搜索的子字符串position在 str 中搜索 searchString 的开始位置,默认值为 0,也就是真正的字符串开头处

    ctx.response.body = '

endsWith()

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 = '

方法用于测试字符串是否以指定的后缀结束。如果参数表示的字符序列是此对象表示的字符序列的后缀,则返回 true,否则返回 false

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 = `

stringObject.substring(start,stop)

Hello, ${name}!

`;

});

router.get('/', async (ctx, next) => {

    ctx.response.body = '

substring()方法用于提取字符串中介于两个指定下标之间的字符

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 = `

start必需。一个非负的整数,规定要提取的子串的第一个字符在 stringObject 中的位置

Welcome, ${name}!

`;

    } else {

        ctx.response.body = `

stop可选。一个非负的整数,比要提取的子串的最后一个字符在 stringObject 中的位置多 1。

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

精简入口文件(app.js)

将以上代码复制一份,命名为controller.js,优化代码,如下

var fs = require('fs') // 引入 Node.js 核心模块 fsconst router = require('koa-router')(); // 引入 koa-router// 函数 addMapping 包含两个参数// router koa-router 实例// mapping 所有路由 urlfunction addMapping(router, mapping) { for (var url in mapping) { // 此时的 mapping 为每一个 .js 文件的 exports 对象,循环该对象 if (url.startsWith('GET ')) { // 如果对象键名 url 已 'GET ' 开头 var path = url.substring(4); // 提取键名中的路径部分,此处是 '/' router.get(path, mapping[url]); } else if (url.startsWith('POST ')) { // 如果对象键名 url 已 'POST ' 开头 var path = url.substring(5); // 提取键名中的路径部分,此处是 '/signin' router.post(path, mapping[url]); } else { console.log(`invalid URL: ${url}`); } }}function addControllers(router, dir) { // 通过 fs.readdirSync 同步读取 /controllers 目录下的文件// 该源实例 index.js 存放于 /controllers 目录下 var files = fs.readdirSync(__dirname   '/'   dir); // 过滤该目录下所有 .js 后缀的文件 var js_files = files.filter((f) = { return f.endsWith('.js'); }); // 循环所有 .js 后缀的文件 for (var f of js_files) { // 动态引入每一个 js 文件,此处并非真实引入 let mapping = require(__dirname   '/'   dir   '/'   f); // 调用 addMapping 函数,并将 addMapping(router, mapping); }}module.exports = function (dir) { let controllers_dir = dir || 'controllers', // 如果不传参数,扫描目录默认为'controllers' router = require('koa-router')(); // 动态引入 koa-router 组件 addControllers(router, controllers_dir); // 实参 router 为实例化 router // 实参 controllers_dir 为路径 return router.routes(); // add router middleware};

这样一来,我们在app.js的代码又简化了

// 导入 controller middleware:const controller = require('./controller');// 使用 middleware:app.use(controller());

文章已同步我的个人博客:《Node学习笔记 初识Koa》

本文由yzc216亚洲城发布于网站首页,转载请注明出处:刺探Koa和创设路由实例

关键词: yzc216亚洲城