Hapi 官方教程:Routes

Hapi 官方教程:Routes

注:截止本文完成时,hapi 的版本为:16.4.3

在我个人的认知中,route(路由)是任何一个框架中最为基础,也是最为重要的部分。

首先,假设我的练习项目的目录结构为:

.
├── package.json
├── public
│   ├── doom.jpg
│   └── doom3.jpg
└── server.js

目录 public 下的2张图片用作练习,随便找2张即可。

Hapi route 的基本形式

和其它许多支持设定 route 的框架一样,hapi 的 route 由3个基本要素组成:

  1. method 即请求方式,如 POST,GET 等等。
  2. path 路径,如“/dog”,“/dog/name”。
  3. handler 没必要直译为“句柄”了吧……就是当访问这个 route 时 server 端会做出什么反应。

所以,一个最简单的 hapi 程序就是在 server.js 文件中写下:

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server();

server.connection({ port: 3000, host: 'localhost' });

server.route({
        method: 'GET',
        path: '/article',
        handler: function (request, reply) {
            reply('Article list');
        }
});

server.start((err) => {

    if (err) {
        throw err;
    }

    console.log('Server running at:', server.info.uri);
});

然后访问 http://localhost:3000/article 时就会看到输出“Article list”。

Route 中带有参数

上面的例子中,当用户访问 /article 时会返回一个“Article list(文章列表)”,那如果用户想要访问 id 为123的那篇文章,对应的 route 怎么写呢?很简单,我们再增加一个 route 配置就行了,如下:

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server();

server.connection({ port: 3000, host: 'localhost' });

//显示文章列表
server.route({
        method: 'GET',
        path: '/article',
        handler: function (request, reply) {
            reply('Article list');
        }
});

//显示特定 ID 的文章内容
server.route({
    method: 'GET',
    path: '/article/{id}', //Path 中用“{}”括起来的部分就表示 route 中的参数名称。
    handler: function (request, reply) {
        var article_id = request.params.id; //获取参数的值也很直观。

        reply('Article ID is: ' + article_id);
    }
});

server.start((err) => {

    if (err) {
        throw err;
    }

    console.log('Server running at:', server.info.uri);
});

此时,如果访问 http://localhost:3000/article/123 会看到输出“Article ID is: 123”。

Route 中带有可选的参数

虽然现在可以看到 ID 为123的文章了,但假如用户忘记了输入“123”而访问 http://localhost:3000/article/ 时就会看到错误提示:

{
    statusCode: 404,
    error: "Not Found",
    message: "Not Found"
}

如何让 ID 成为可选的呢?当没有输入具体 ID 时给予用户一个友好的提示信息。办法是修改“/article/{id}”为“/article/{id?}”。修改后的 server.js 如下:

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server();

server.connection({ port: 3000, host: 'localhost' });

//注意,这里取掉了原有的“/article”的 route 定义。因为当“/article/{id}”中的“id”变为可选后,其定义就和“/article”相冲突了。
//在冲突的情况下如果运行 server.js 会看到错误提示“Error: New route /article/{id?} conflicts with existing /article”。

//显示特定 ID 的文章内容
server.route({
    method: 'GET',
    path: '/article/{id?}', //为 path 中的参数加上“问号”就表示该参数是可选的。
    handler: function (request, reply) {
        var article_id = request.params.id ? request.params.id : false;

        if (article_id) {
            reply('Article ID is: ' + article_id);
        } else {
            reply('找不到对应的文章');
        }
    }
});

server.start((err) => {

    if (err) {
        throw err;
    }

    console.log('Server running at:', server.info.uri);
});

当再访问 http://localhost:3000/article/ 时就会看到比较友好的提示信息“找不到对应的文章”。

Route 中可选参数的不合法定义方式

简单的说就是,只有 route 中的“最后一个”参数可以被设定为可选参数,即:

  • /article/{id?} 合法
  • /article/{id?}/{author?} 不合法

很容易记的规则,对吧?

Route 的匹配顺序

假设我们设定了2个 route,分别为:

  1. /filename.jpg
  2. /filename.{ext}

此时,如果我们访问 http://localhost:3000/filename.jpg,那么负责响应的将会是第一个 route,而不是第二个。

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server();

server.connection({ port: 3000, host: 'localhost' });

server.route({
    method: 'GET',
    path: '/filename.jpg',
    handler: function (request, reply) {
        reply('I am the 1st route.');
    }
});

server.route({
    method: 'GET',
    path: '/filename.{ext}',
    handler: function (request, reply) {
        reply('I am the 2nd route.');
    }
});

server.start((err) => {

    if (err) {
        throw err;
    }

    console.log('Server running at:', server.info.uri);
});

所以,牢记是第一个匹配的 route 生效。

Route 中的通配符

先说一个 route 中的概念“segment(分割)”,所谓“分割”就是指 path 中的“/”,比如“/greet/Jack”这个 route 中就有2个 segment:greet 部分和 Jack 部分。

假设我们定义一个 route 用来向人们问好,代码应该是:

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server();

server.connection({ port: 3000, host: 'localhost' });

server.route({
    method: 'GET',
    path: '/greet/{name}',
    handler: function (request, reply) {
        const name = request.params.name;

        reply('Hi! ' + name);
    }
});

server.start((err) => {

    if (err) {
        throw err;
    }

    console.log('Server running at:', server.info.uri);
});

当我们访问 http://localhost:3000/greet/Jack 时会看到“Hi! Jack”。但如果我们想要以 http://localhost:3000/greet/Jack/Tome/Bill 的形式同时问候多个人的时候就会报错。因为在 hapi 看来,'/greet/{name}' 中的 name 对应的只是 greet 之后的那个 segment,而多出来的部分就无法匹配到相应的 route 了。这种情况下,可以使用“通配符”。让我们修改代码为:

server.route({
    method: 'GET',
    path: '/greet/{name*3}', //这里加上了一个通配符“*3”
    handler: function (request, reply) {
        const name = request.params.name;

        reply('Hi! ' + name);
    }
});

现在,再访问 http://localhost:3000/greet/Jack/Tome/Bill 看看,会得到输出“Hi! Jack/Tome/Bill”。很好,可如果我们再多加一个人名呢?访问 http://localhost:3000/greet/Jack/Tome/Bill/Max 看看,又报错了吧?我们再次修改代码:

server.route({
    method: 'GET',
    path: '/greet/{name*}', //这里的通配符去掉了“3”,只留下“*”。
    handler: function (request, reply) {
        const name = request.params.name;

        reply('Hi! ' + name);
    }
});

现在再访问 http://localhost:3000/greet/Jack/Tome/Bill/Max 试试,可以正常得到输出了吧?

联系前后来看,我们就可以明白 route 中通配符的工作原理了:当通配符“*“配合数字使用时表示匹配具体的 segment 数量。而当舍弃数字使用时,就表示匹配所有的 segment 了。也正因为这个特定,所以通配符只能用在 route 中的最后一个 segment 中。

另外需要注意的时,当使用通配符时,接收到的参数是带有“/”符号的,比如“Jack/Tome/Bill/Max”。如果想要区分它们,只能自己处理,比如:

server.route({
    method: 'GET',
    path: '/greet/{name*}',
    handler: function (request, reply) {
        const name = request.params.name.split('/'); //分割处理

        reply('Hello ' + userParts[0] + ' ' + userParts[1] + '!');
    }
});

Route 中的 handler

Route 中的 handler 是一个函数,该函数接收2个参数:request 和 reply,顾名思义一个负责处理来自外界的请求,一个负责处理响应和输出。这2个参数都是 object,也没什么可讲的,模仿范例多去用就明白了。两者的具体参考见 API 文档:

Route 中的 config

在 hapi 的 route 中定义中还可以传入一个 config 对象,类似:

server.route({
    method: 'GET',
    path: '/hello/{user?}',
    handler: function (request, reply) {
        const user = request.params.user ? encodeURIComponent(request.params.user) : 'stranger';
        reply('Hello ' + user + '!');
    },
    config: {
        description: 'Say hello!',
        notes: 'The user parameter defaults to \'stranger\' if unspecified',
        tags: ['api', 'greeting']
    }
});

用官方的话说就是,这个 config 对象在代码的运行上什么用都没有,但是配合某些插件后可以自动为你的 route 生成说明文档。现在明白为什么说 hapi 适合用来写 API 服务了吧。

results for ""

    No results matching ""