Koa2学习系列03-路由koa-router——MVC 中重要的环节:Url 处理器

路由 koa-router

上一节我们学习了中间件的基本概念,本节主要带大家学习下 koa-router 路由中间件的使用方法。

路由是用于描述 URL 与处理函数之间的对应关系的。比如用户访问 http://localhost:3000/,那么浏览器就会显示 index页面的内容,如果用户访问的是 http://localhost:3000/home,那么浏览器应该显示 home 页面的内容。

要实现上述功能,如果不借助 koa-router 或者其他路由中间件,我们自己去处理路由,那么写法可能如下所示:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
   if (ctx.request.path === '/') {
       ctx.response.body = '<h1>index page</h1>';
   } else {
       await next();
   }
});
app.use(async (ctx, next) => {
   if (ctx.request.path === '/home') {
       ctx.response.body = '<h1>home page</h1>';
   } else {
       await next();
   }
});
app.use(async (ctx, next) => {
   if (ctx.request.path === '/404') {
       ctx.response.body = '<h1>404 Not Found</h1>';
   } else {
       await next();
   }
});

app.listen(3000, ()=>{
 console.log('server is running at http://localhost:3000')
})

把上述代码复制并覆盖到 app.js 中,然后执行以下命令启动 node 程序:

node app.js

启动之后在浏览器中分别访问 http://localhost:3000/http://localhost:3000/homehttp://localhost:3000/404 就能看到相应的页面了。

上述 app.js 的代码中,由 async 标记的函数称为『异步函数』,在异步函数中,可以用 await 调用另一个异步函数,asyncawait 这两个关键字将在 ES7 中引入。参数 ctx 是由 koa 传入的,我们可以通过它来访问 requestresponsenextkoa 传入的将要处理的下一个异步函数。

注意: 由于 nodev7.6.0 中才支持 asyncawait,所以在运行 app.js 之前请确保 node 版本正确,或者使用一些第三方的 async 库来支持。

这样的写法能够处理简单的应用,但是,一旦要处理的 URL 多起来的话就会显得特别笨重。所以我们可以借助 koa-router 来更简单的实现这一功能。 下面来介绍一下如何正确的使用 koa-router

安装 koa-router

通过 npm 命令直接安装:

npm i koa-router -S

-S 或者 --save 是为了安装完成之后能够在 package.jsondependencies 中保留 koa-router,以便于下次只需要执行 npm i 或者 npm install 就能够安装所有需要的依赖包。

基本使用方法

如果要在 app.js 中使用 koa-router 来处理 URL,可以通过以下代码来实现:

const Koa = require('koa')
// 注意 require('koa-router') 返回的是函数:
const router = require('koa-router')()
const app = new Koa()

 // 添加路由
 router.get('/', async (ctx, next) => {
    ctx.response.body = `<h1>index page</h1>`
})

router.get('/home', async (ctx, next) => {
    ctx.response.body = '<h1>HOME page</h1>'
})

router.get('/404', async (ctx, next) => {
    ctx.response.body = '<h1>404 Not Found</h1>'
})

 // 调用路由中间件
 app.use(router.routes())

app.listen(3000, ()=>{
  console.log('server is running at http://localhost:3000')
})

运行 app.js:

node app.js

执行完上面的操作之后,我们在浏览器中访问 http://localhost:3000/

[图片上传中...(image-df51e-1589252096343-2)]

在浏览器中访问 http://localhost:3000/home

[图片上传失败...(image-3565e1-1589252096343)]

在浏览器中访问 http://localhost:3000/404

[图片上传失败...(image-79f744-1589252096343)]

通过上面的例子,我们可以看到和之前不使用 koa-router 的显示效果是一样的。不过使用了 koa-router 之后,代码稍微简化了一些,而且少了 if 判断,还有省略了 await next()(因为没有其他中间件需要执行,所以这里就先省略了)。

当然,除了 GET 方法,koa-router 也支持处理其他的请求方法,比如:

router
  .get('/', async (ctx, next) => {
    ctx.body = 'Hello World!';
  })
  .post('/users', async (ctx, next) => {
    // ... 
  })
  .put('/users/:id', async (ctx, next) => {
    // ... 
  })
  .del('/users/:id', async (ctx, next) => {
    // ... 
  })
  .all('/users/:id', async (ctx, next) => {
    // ... 
  });

在任意http请求中,遵从 RESTful 规范,可以把 GETPOSTPUTDELETE 类型的请求分别对应 ,这里 router 的方法也一一对应。通常我们使用 GET 来查询和获取数据,使用 POST 来更新资源。PUTDELETE 使用比较少,但是如果你们团队采用 RESTful架构,就比较推荐使用了。我们注意到,上述代码中还有一个all 方法。all 方法通常用于匹配一组路由或者全部路由从而做一些统一设置和处理,也可以处理不确定客户端发送的请求方法类型的情况。

举个例子,假设客户端使用 jQuery 来开发,有如下几个 ajax 请求:

// 优先匹配和 router.get 方法中 url 规则一样的请求,如果匹配不到的话就匹配和 router.all 方法中 url 规则一样的请求。
$.ajax({
  method: "GET",
  url: "some.php",
  data: { name: "John" }
}).done(function( msg ) {
  // do something 
});

// 优先匹配和 router.post 方法中 url 规则一样的请求,如果匹配不到的话就匹配和 router.all 方法中 url 规则一样的请求。
$.ajax({
  method: "POST",
  url: "some.php",
  data: { name: "John" }
}).done(function( msg ) {
  // do something 
});

上面例子中两个方法最主要的区别就是 ajaxmethod 的值,method 的值和 router 的方法一一对应。
再举一个使用all方法的例子,假设我们要为所有请求设置跨域头,可以通过如下代码实现:

router.all('/*', async (ctx, next) => {
 // *代表允许来自所有域名请求
 ctx.set("Access-Control-Allow-Origin", "*");
 // 其他一些设置...
 await next();
});

这段代码表示对于所有请求,允许来自所有域名。这是一种很危险的做法,在真实项目中一定不能这么做。

* 号是一种通配符,表示匹配任意 URL。这里的返回是一种简化的写法,真实开发中,我们肯定要去读取 HTML 文件或者其他模板文件的内容,再响应请求。关于这部分的内容后面的章节中会详细介绍。

另外,如果一条路由在all方法和其他方法中同时命中,只有执行了await next(),那么这条路由会在all方法和其他方法中都会起作用,举个例子,看如下代码:

const Koa = require('koa')
const router = require('koa-router')()
const app = new Koa()

// 添加路由
router.get('/', async (ctx, next) => {
  ctx.response.body = `<h1>index page</h1>`
  await next();
})
router.all('/', async (ctx, next) => {
  console.log('match "all" method')
  await next();
});
// 调用路由中间件
app.use(router.routes())

app.listen(3000, ()=>{
  console.log('server is running at http://localhost:3000')
})

执行这段代码,我们不仅能够访问http://localhost:3000/看到“index page”,也能够在控制台中看到“'match "all" method'”,说明路由"/"不仅执行了get方法的回调,也执行了all方法的回调函数。但是,如果我们把get方法中的await next()去掉,那么就不会命中all方法的路由规则,也不会执行all方法的回调函数了。因为说到底,对路由的处理也是一种中间件,如果不执行await next()把控制权交给下一个中间件,那么后面的路由就不会再执行了。

其他特性

命名路由

在开发过程中我们能够很方便的生成路由 URL

router.get('user', '/users/:id', function (ctx, next) {
  // ... 
});

router.url('user', 3);
// => 生成路由 "/users/3" 

router.url('user', { id: 3 });
// => 生成路由 "/users/3" 

router.use(function (ctx, next) {
  // 重定向到路由名称为 “sign-in” 的页面 
  ctx.redirect(ctx.router.url('sign-in'));
})

router.url 方法方便我们在代码中根据路由名称和参数(可选)去生成具体的 URL,而不用采用字符串拼接的方式去生成 URL了。

多中间件

koa-router 也支持单个路由多中间件的处理。通过这个特性,我们能够为一个路由添加特殊的中间件处理。也可以把一个路由要做的事情拆分成多个步骤去实现,当路由处理函数中有异步操作时,这种写法的可读性和可维护性更高。比如下面的示例代码所示:

router.get(
  '/users/:id',
  function (ctx, next) {
    return User.findOne(ctx.params.id).then(function(user) {
      // 首先读取用户的信息,异步操作
      ctx.user = user;
      next();
    });
  },
  function (ctx) {
    console.log(ctx.user);
    // 在这个中间件中再对用户信息做一些处理
    // => { id: 17, name: "Alex" }
  }
);

嵌套路由

我们可以在应用中定义多个路由,然后把这些路由组合起来用,这样便于我们管理多个路由,也简化了路由的写法。

var forums = new Router();
var posts = new Router();

posts.get('/', function (ctx, next) {...});
posts.get('/:pid', function (ctx, next) {...});
forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());

// 可以匹配到的路由为 "/forums/123/posts" 或者 "/forums/123/posts/123"
app.use(forums.routes());

路由前缀

通过 prefix 这个参数,我们可以为一组路由添加统一的前缀,和嵌套路由类似,也方便我们管理路由和简化路由的写法。不同的是,前缀是一个固定的字符串,不能添加动态参数。

var router = new Router({
  prefix: '/users'
});

router.get('/', ...); // 匹配路由 "/users" 
router.get('/:id', ...); // 匹配路由 "/users/:id" 

URL 参数

koa-router 也支持参数,参数会被添加到 ctx.params 中。参数可以是一个正则表达式,这个功能的实现是通过 path-to-regexp 来实现的。原理是把 URL 字符串转化成正则对象,然后再进行正则匹配,之前的例子中的 * 通配符就是一种正则表达式。

router.get('/:category/:title', function (ctx, next) {
  console.log(ctx.params);
  // => { category: 'programming', title: 'how-to-node' } 
});

通过上面的例子可以看出,我们可以通过 ctx.params 去访问路由中的参数,使得我们能够对参数做一些处理后再执行后续的代码。

使用了 koa-router 之后,代码简洁了很多。下一节中,我们将学习下如何响应浏览器的各种请求。

原文https://github.com/ikcamp/koa2-tutorial/tree/2-koa-router

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351