Hapi框架总结

学习node框架hapi的总结

Posted by wang chong on February 9, 2019

hapi

hapi是一款用于构建应用程序和服务的丰富框架。它的使命在于配置优于代码,业务逻辑必须和传输层进行分离。

使用hapi搭建一个简单的web应用

初始化项目

npm init -y

安装hapi

npm install hapi –save

在项目根目录创建app.js文件,代码如下:

const Hapi = require('hapi');   //引入hapi
const server = Hapi.server({    //创建服务
    host : 'localhost',
    port : 8080,
})
const start = async ()=>{
    //注册路由
    server.route({
        method : 'GET',
        path : '/',
        handler : (request,h) => {
            return 'Hello World';
        }
    })
    //启动服务
    await server.start();
    console.log(`Server is running:${server.info.uri}`);
}
start();
//监听错误信息
process.on('unhandleRejection',(err)=>{
    console.log(err);
    process.exit(1);
})

启动项目

node app.js

访问127.0.0.1:8080。

hapi路由

hapi的路由和Express、koa等路由有些不太相同,也突出了它的配置优于代码的观点。

路由管理

在项目根目录下创建routes文件夹,在其中创建index.js作为路由管理中心。例如:

//test.js中
const test = {
    method : 'GET',
    path : '/test',
    handler : (request,h) = {
        return 'test';
    }
}
    //test.js中的路由在这里导出
module.exports = [
    test,
]
//hello.js中
const hello = {
    method : 'GET',
    path : '/hello',
    handler : (request,h) => {
        return 'Hello world';
    }
}
    //hello.js中注册的路由在这里导出
module.exports = [
    hello,
]
//index.js中---路由注册中心
    //把子路由导入这里
module.exports = [
    require('./test.js'),
    require('./hello.js')
]

//在app.js中
const routes = require('./routes');
//将所有的路由进行注册
const start = ()=>{
    await routes.forEach(item=>server.route(item));
}

上述代码中使用index路由进行路由的统一管理与注册。

路由方法

如果想让一个路由同时具有多个HTTP方法,可以把method属性改成数组。

server.route({
    method : ['GET','POST','PUT'],
    path : '/',
    handler : (request,h) => {
        return 'Hello world';
    }
})

路由路径

hapi中的路由路径必须是一个字符串,如果想在路径中使用参数必须用{}包裹起来,相当于其他框架中的路由参数,同哟request.params来取得路由参数。

server.route({
    method : 'GET',
    path : '/test/{name}',
    handler : (request,h) => {
        return `Hello ${request.params.name};`
    }
})

路径的可选参数

在上面例子中,name参数是必须有的,没有的话就会报错。在参数后面加?的话就把这个参数改成了可选的。

server.route({
    method : 'GET',
    path : '/test/{name?}',
    handler : (request,h) => {
        return `Hello ${request.params.name};`
    }
})

路由参数的多段参数

在上面的例子中,在name后面加上*就表示这个参数是多段的,例如:/test/a/b、/test/a/b/c等等。同时如果星号后面加数字的话,就确定了段数。

server.route({
    method : 'GET',
    path : '/test/{name*}',
    //path : '/test/{name * 2}'
    handler : (request,h) => {
        return `Hello ${request.params.name};`
    }
})

路由中的handler方法

handler方法是一个接受两个参数的函数,一个是request、一个是h。

  1. request 是用户请求对象的具体信息,例如参数、请求内容数据, 授权信息以及http头部信息等。
  2. h 是响应处理对象,它包含了一些处理请求的方法。

hapi插件

hapi 拥有一个可扩展并且强健的插件系统,它允许你将应用分割为独立的业务逻辑或者可重用的组件。hapi拥有server.register方法用来注册插件。

载入插件

插件可以一次载入一个,也可以通过方法 server.register() 分组载入

const start = async function () {
    // 载入一个插件
    await server.register(require('myplugin'));
    // 载入多个插件
    await server.register([require('myplugin'), require('yourplugin')]);
};

要将选项传递给插件时,我们将传递一个拥有 plugin 和 options 作为键的对象。

const start = async function () {
    //载入单个
    await server.register({
        plugin: require('myplugin'),
        options: {
            message: 'hello'
        }
    });
    //载入多个
    await server.register([{
        plugin: require('plugin1'),
        options: {}
    }, {
        plugin: require('plugin2'),
        options: {}
    }]);
};

自己创建一个插件

插件的核心是一个拥有register属性的对象,其属性值是一个拥有async function(server,options)签名的函数。除此之外还有name、version等其他属性。

const myPlugin = {
    //当作为一个外部模块时,需要指定pkg,
    pkg:require('./package.json'),
    name : 'myPlugin',
    version : 1.0.0,
    register : async (server,options) => {
         server.route({
            method: 'GET',
            path: '/test',
            handler: function (request, h) {
                return 'hello, world';
            }
        });
        //其他方法
    }
}

Cookie作为数据持久化在服务端是非常重要的一个了。hapi使用cookie首先必须要使用server.state(name,[options])来配置

server.state('data',{   //cooki的名字
    ttl : null,  //cookie的生存时间
    isSecure:true,  //是否是安全的
    isHttpOnly : true, //是否只支持HTTP
    encodeing : 'base64json', //base64编码的json字符串
    clearInvalid :false,
    strictHeader : true,
})

设置一个Cookie

在路由中设置cookie通过h.state(name,value,[options])方法来设置

server.route({
    method : 'GET',
    path : '/',
    handler : (request,h) =>{
        h.state('data'.'test');//cookie名为data 值为test
        return  h.response('Hello');
    }
})
第三个参数

可以通过第三个参数来单独设置cookie的配置,例如,不使用编码。

server.route({
    method : 'GET',
    path : '/',
    handler : (request,h) =>{
        h.state('data'.'test',{encoding:none});
        return  h.response('Hello');
    }
})
链式调用

路由的h上的方法可以链式调用,显得更加方便。

return h.response('Hello').state('data', 'test', { encoding: 'none' });

获取一个Cookie

如果想在服务端获取一个cookie,使用request上的state属性就可以获取,例如上面注册了一个data的cookie

console.log(request.state.data);

清理Cookie

在服务端h上调用unstat(name)方法,可以清除cookie。注意的是:这个只是将cookie的值清空,并未删除这个cookie

return h.response('Hello').unstate('data')

静态内容

如果想在hapi中引入静态内容,必须引入inert插件

npm install inert –save

在app.js中注册插件

const start = async () => {
    await server.register(require('inert'));
}

h.file(path, [options])

使用可以方法可以指定一个文件输出,并且只能是绝对路径

const path = require('path');
 server.route({
    method: 'GET',
    path: '/index',
    handler: function (request, h) {
        return h.file(path.join(__dirname,'..','index.html'));
    }
});

相对路径

如果想在路由中使用相对路径,一个办法就是在服务器配置route的基本路径,之后之将相对路径传递给h.file()就可以了。

//app.js中
const server = Hapi.server({
    routes: {
        files: {
            relativeTo: Path.join(__dirname, 'public')
        }
    }
});
//route中
 server.route({
    method: 'GET',
    path: '/index',
    handler: function (request, h) {
        return h.file('./index.html');
    }
});

文件handler

开始就说hapi这个框架是配置优于代码。这里也是充分体现,上述代码handler是一个函数,到文件handler这里就是对象属性配置了。

 server.route({
    method: 'GET',
    path: '/index',
    handler: {
        file : function(request) {
            return 'index.html';
        }
    }
});

这个file还可以继续配置化。它也可以是具有 path 属性的对象。当在 handler 中使用对象时,我们可以附加一些别的东西,如设 Content-Disposition 头并允许压缩文件。如下:

server.route({
    method: 'GET',
    path: '/script.js',
    handler: {
        file: {
            path: 'script.js',
            filename: 'client.js', // 修改 Content-Disposition 头中的文件名
            mode: 'attachment', // 指定 Content-Disposition 是一个附件
            lookupCompressed: true // 如果请求允许,将允许查找 script.js.gz
        }
    }
});

目录handler

除了文件handler 还可以目录handler目录handler

数据验证

验证通过joi这个模块进行验证。

路径参数

在路由的pat中使用命名参数的时候,可以指定命名参数的类型及长度。当不符合标准的时候会得到HTTP400响应错误。

server.route({
    method: 'GET',
    path: '/hello/{name}',
    handler: function (request, h) {
        return `Hello ${request.params.name}!`;
    },
    options: {
        validate: {
            params: {
                name: Joi.string().min(3).max(10)
            }
        }
    }
});

查询参数

查询验证是GET方法中带的参数,类似于上面。默认情况下 hapi 不做任何验证,但是如果一但为一个查询参数添加验证时,这将意味着你 必须 为你所接受的所有查询参数添加验证。

server.route({
    method: 'GET',
    path: '/posts',
    handler: function (request, h) {
        return request.query.limit;
    },
    options: {
        validate: {
            query: {
                limit: Joi.number().integer().min(1).max(100).default(10)
            }
        }
    }
});

其他方法参考数据验证的其他方法

视图

几乎所有的node框架都支持服务端渲染模板的,这个也不例外,也是支持的。hapi的服务端渲染引擎、部分渲染、辅助工具 (模板中用于操作数据的函数)以及布局功能。都是vision这个插件提供了。因此使用的时候必须要注册这个插件。hapi使用的服务端渲染的模板引擎是handlbars。

安装插件和模板引擎

npm install vision handlebars –save

配置模板引擎

const start = async () => {
    await server.register(require('vision'));
    server.views({
        engines: {
            html: require('handlebars')
        },
        relativeTo: __dirname,  //这里是一些路径参数
        path: 'templates'
    });
};
  1. path : 包含你主要模板的路径
  2. partialsPath : 部分内容的路径
  3. helpersPath : 模板辅助工具的路径
  4. layoutPath : 布局模板的路径
  5. relativeTo : 用于指定其他路径的前缀。当指定后,其余路径则都可以相对于此路径

isCached

如果设置为 false , hapi 将不会缓存模板引擎渲染的结果,而是每次都去读取模板文件并渲染。 在开发阶段非常方便。

渲染一个视图

渲染视图有两种方式:一种是通过h.view(),一种是通过视图handler

h.view()
server.route({
    method: 'GET',
    path: '/',
    handler: function (request, h) {
        return h.view('index');
    }
});

h.view()的第二个参数可以指定传递要渲染的内容

return h.view('index', { title: 'My home page' });
视图 handler
server.route({
    method: 'GET',
    path: '/',
    handler: {
        view: 'index'
    }
});

当使用视图 handler 时,内容作为 context 的值传入, 如:

handler: {
    view: {
        template: 'index',
        context: {
            title: 'My home page'
        }
    }
}

全局内容传递

最简单的方式是在调用 server.views() 使用 context 选项

const context = {
    title: 'My personal site'
};

server.views({
    engines: {
        html: {
            module: require('handlebars'),
            compileMode: 'sync' // engine specific
        }
    },
    context
});

视图辅助工具

视图的辅助工具同样的也是服务端渲染。

在views下面创建一个helpers文件夹,创建fortune.js 文件,内容如下:

module.exports = function () {

    const fortunes = [
        'Heisenberg may have slept here...',
        'Wanna buy a duck?',
        'Say no, then negotiate.',
        'Time and tide wait for no man.',
        'To teach is to learn.',
        'Never ask the barber if you need a haircut.',
        'You will forget that you ever knew me.',
        'You will be run over by a beer truck.',
        'Fortune favors the lucky.',
        'Have a nice day!'
    ];

    const x = Math.floor(Math.random() * fortunes.length);
    return fortunes[x];
};

在views下创建index.html内容如下

<h1>Your fortune</h1>
<p></p>  <!--这里注意一定要和helpers中的文件的名字相同-->

布局

vision 对于视图布局有一些内建的支持,这个功能默认是关闭的,因为它可能会和一些特定的模板引擎冲突,因此我们建议只使用一种布局系统。

如果要使用内建的布局系统,首先需要设置视图引擎:

server.views({
    // ...
    layout: true,
    layoutPath: 'views/layout'
});

在views中创建layout文件夹,创建layout.html,内容如下:

<html>
  <body>
 </body>
</html>

之后的视图就不需要写骨架了。

<div>Content</div>

当渲染这个视图时 将会被视图中的内容替换。

可以通过全局配置来修改这个默认布局:

server.views({
    // ...
    layout: 'another_default'
});

你也可以为每一个视图指定一个布局:

return h.view('myview', null, { layout: 'another_layout' });