随身笔记
随身笔记

hapi笔记/教程

本教程基于hapi@13.5.3

"dependencies": {
"boom": "^3.2.2",
"ejs": "^2.5.7",
"good": "^6.6.3",
"good-console": "^5.3.2",
"hapi": "^13.5.3",
"hapi-auth-cookie": "^6.1.1",
"hapi-mobile-detect": "^1.3.5",
"inert": "^4.2.1",
"joi": "^8.4.2",
"netmask": "^1.0.5",
"vision": "^4.1.1"
}

hapi

基础包:hapi_www

 

教程基于npm install –save hapi@13.5.3

1,搭建http

var Hapi = require(‘hapi’);
var server=new Hapi.Server();
server.connection({port:4000});
server.route([{
method:’GET’,
path:’/’,
handler:function (request,reply) {
reply({name:’234′});
}
}]);

 

server.start((err)=>{
console.log(‘正常’);
});

 

访问:http://127.0.0.1:4000/

 

 

 

2,在shell窗口命令里面,实时监听http访问记录日志

 

var Hapi = require(‘hapi’);
var server=new Hapi.Server();
server.connection({port:4000});
server.route([{
method:’GET’,
path:’/’,
handler:function (request,reply) {
reply({name:’234′});
}
}]);

 

//需要安装 npm install –save good@6 good-console@5
server.register([{
register:require(‘good’),
options:{
reporters:[{
reporter:require(‘good-console’),
events:{response:’*’}
}]
}
}],(err)=>{
server.start((err)=>{
console.log(‘正常’);
});
});

 

 

 

3,路由分离,(把一些路由独立出来)

 

//route.js

module.exports=[{
method:’GET’,
path:’/haha’,
handler:function (request,reply) {
reply(‘你好哈哈’);
//this.db 这样可以找到数据库
}
}];

 

 

//index.js

var Hapi = require(‘hapi’);

var Sqlite3=require(‘sqlite3’);
var db1=new Sqlite3.Database(‘./db’)

var server=new Hapi.Server();
server.connection({port:4000});
server.route([{
method:’GET’,
path:’/’,
handler:function (request,reply) {
reply({name:’234′});
}
}]);

 

server.bind({db:db1}); //能保证引入的外来路由可以在handler里面的this.db找到数据库

//调用:
server.route(require(‘./route’));

 

 

 

4,传参。将父页面的参数,传入给引入的子模块中

server.bind({db:’数据’});
server.route(require(‘./route’)); //在route.js中的handler里面可以使用this.db来得到数据

//route.js
module.exports=[{
method:’GET’,
path:’/’,
handler:function (request,reply) {
this.db //打印出:数据
}
}];

 

 

 

5,ejs模板
npm install ejs –save

server.register([{
register:require(‘good’),
options:{
reporters:[{
reporter:require(‘good-console’),
events:{response:’*’}
}]
}
},
require(‘inert’),
require(‘vision’), //加载模板,可以使用server.views()方法
],(err)=>{

server.views({ //配置模板
engines:{
ejs:require(‘ejs’) //npm install ejs –save
},
relativeTo:__dirname,
path:’./views’,
isCached:false //建议正式上线改为true
});

server.route(require(‘./route’));
server.start((err)=>{
console.log(‘正常’);
});

 

});

 

在views目录中存放index.ejs
内容: <%= title %>

 

使用:
handler:function (request,reply) {
reply.view(‘index’,{
title:request.params.id
});
}

 

 

 

 

6,后台接收get、post数据

接收get
method:’GET’,
path:’/test/{id}’,
handler:function (request,reply) {
reply.view(‘index’,{
title:request.params.id
});
}

 

接收post
<form method=”post” action=”/post_test”>
<input type=”text” name=”user”>
<input type=”submit” value=”提交”>
</form>

 

method:’POST’,
path:’/post_test’,
config:{
payload:{
output:’data’
}
},
handler:function (request,reply) {
console.log(‘post数据’+request.payload.user);
}

 

 

7, cookies

npm install –save hapi-auth-cookie@6

 

server.register([{
register:require(‘good’),
options:{
reporters:[{
reporter:require(‘good-console’),
events:{response:’*’}
}]
}
},
require(‘inert’),
require(‘vision’),
require(‘hapi-auth-cookie’), //加载cookies
],(err)=>{

if(err){
throw err;
}

//设置cookies
server.auth.strategy(‘session’,’cookie’,’try’,{
password:’password-that-is-at-least-32-chars’, //加密方式
ttl: 24 * 3600 * 1000, //1天后过期,如果不设置一旦关闭页面cookies值就消失
isSecure:false, //true时,只允许https使用
});

 

});

 

设置:
handler:function (request,reply) {
request.cookieAuth.set({
name:encodeURIComponent(request.params.id)
});

reply.view(‘index’,{
title: request.params.id
});
}

 

取值:
request.auth.credentials.name

 

清除:
request.cookieAuth.clear();

 

判断是否存在cookies:
request.auth.isAuthenticated //返回布尔值

 

 

 

8,页面跳转
reply.redirect(‘/’);

 

 

9,路由路径

path:’/images/{id}’
http://127.0.0.1/images/sdf

 

//可选参数
path:’/images/{id?}’
http://127.0.0.1/images/sdf
或者
http://127.0.0.1/images/

 

//通配符
path:’/{id*}’
这个路径是全能型匹配,静态资源就是靠通配符配置的

 

 

 

10,服务器方法

类似 把多个路由里面的公共方法抽出来独立成一个公共的方法。

创建:
server.method({
name:’mean123′,
method:function (values,next) {
// var sum=values.reduce((a,b)=>{a+b});
return next(null,values);
},
options:{}
});

 

调用:(要在路由里面的handler里面调用)
handler:function (request,reply) {
server.methods.mean123(JSON.parse(request.payload.user),(err,mean)=>{
console.log(JSON.stringify(mean));
return reply.view(‘index’,{
title:{mean:JSON.stringify(mean)}[‘mean’]
});
});
}

 

 

11,同步(先决条件)

server.route({
method:’post’,
path:’/post_test’,
config:{
payload:{
output:’data’ //靠request.payload.属性名 来获取。如果<input name=”user”>那么request.payload.user
},
pre:[{
assign:’xx’, //在handler里面靠request.pre.xx获取
method:function (request,reply) { //这里比handler要先执行
setTimeout(function () {
reply(‘数据啊’); //要使用reply才能将数据返回给handler中的request.pre.xx
},5000);

// reply(‘跳过handler函数,直接将数据显示到浏览器’).takeover();

}
}]
},
handler:function (request,reply) { //一定要等pre的method执行完毕后,才轮到handler执行
console.log(‘user:’+request.payload.user); //user是<input name=”user”>
console.log(‘xx:’+request.pre.xx);
reply.view(‘index’,{
title:request.pre.xx
});

}
});

 

 

 

 

 

 

 

12,带有先决条件的服务器方法 (类似:多个方法同步排队执行)

 

声明多个服务器方法:
server.method([{
name:’mean123′,
method:function (request,reply) {
setTimeout(function () {
reply(‘数据123′);
},5000);

},
options:{}
},
{
name:’mean456’,
method:function (request,reply) {
console.log(request.pre.mean123); //可以获取到先执行完毕的服务器方法的数据
reply(‘数据456′);
},
options:{}
}
]);

 

调用:
server.route({
method:’post’,
path:’/post_test’,
config:{
payload:{
output:’data’
},
pre:[‘mean123′,’mean456’] //同步,要等mean123的服务器方法执行完毕后,才轮到mean456执行
},
handler:function (request,reply) {
reply.view(‘index’,{
title:request.pre.mean123+’–‘+request.pre.mean456
});
}
});

 

异步:
例如:
pre:[
[ //这两个服务器方法,属于异步执行,必须两个都完成,才能继续执行
‘abc’,
‘efg’
],
‘mean123’,
‘mean456’
]
//这样的设计解决了,地狱回调的痛苦,而且数据可以共享

 

 

 

13,上传

var path=require(‘path’);
var fs=require(‘fs’);

 

html:
<form method=”post” action=”/upload_page” enctype=”multipart/form-data”>
<input type=”file” name=”upload”>
<input type=”submit” value=”上传”>
</form>

 

 

路由:
//上传
server.route({
method:’post’,
path:’/upload_page’,
config:{
payload:{
output:’stream’,
parse:true,
maxBytes:1048576 *4,//允许上传 4mb
timeout:10000 //上传超时默认10秒
},
},
handler:function (request,reply) {
var upload=request.payload.upload;
var uploadname=path.basename(request.payload.upload.hapi.filename);
var des=path.join(__dirname,’uploads’,uploadname);
upload.pipe(fs.createWriteStream(des));
reply.view(‘index’,{
title:’上传完成’
});
}
});

 

文件会上传存放在根目录的uploads目录下

 

 

 

14,设置黑明单,禁止指定ip不能访问

npm install –save boom@3 netmask@1.0.5

var Boom = require(‘boom’);
var Netmask = require(‘netmask’).Netmask;

 

//黑名单
var blacklist=[
‘127.0.0.0/8’
];

//——————–
var blockips=function (request,reply) {
var ip=request.info.remoteAddress; //获取客户端ip
for(let i=0;i<blacklist.length;i++){
var block=new Netmask(blacklist[i]);
if(block.contains(ip)){ //加!,blacklist就成了白名单
console.log(‘禁止该’+ip+’ip访问’);
return reply(Boom.forbidden()); //如果执行到这步就停止原来的生命周期,返回403
}
}

reply.continue(); //继续其他的生命周期流程走

}
//——————–
server.ext(‘onRequest’,blockips); //扩展点,把自定义好的功能插入到,hapi生命周期中执行(可以理解为,客户端访问任意页面时,总会执行此函数)

 

 

 

15,扩展点
要使用扩展点,就要了解客户端向服务器请求页面的过程,生命周期都有哪些,并在不同的生命周期的阶段中,插入扩展点来实现功能。

可以参看14的案例

 

 

16,客户端reply
也就是负责把服务器返回来的信息显示到浏览器中

 

handler:function(request,reply){
reply(‘你好’); //reply() 返回的是一个response,并不是直接把字符串打印到浏览器上,所以在此过程中我们可以修改返回到浏览器上的数据类型

}

 

reply.view(‘index’,{
title: ‘fff’
})
.type(‘text/plain’) //不解析html,直接把html代码输出到浏览器中
.code(200)
.header(‘x-powered-by’,’hapi’); //服务器的头信息,类似注释作

 

 

server.ext(‘onPreResponse’,(request,reply)=>{ //onPreResponse一般能监听到 静态资源。如:images、css,或者type(‘text/plain’)的链接
console.log(‘VVVVVVVVVVVVVVVVVVVV’);
console.log(request.response.statusCode);
console.log(request.response.headers);
console.log(‘^^^^^^^^^^^^^^^^^^^^’);
reply.continue();
});

 

 

 

17,创建路由

 

独立创建一个路由:
server.route({
method:’get’,
path:’/asf’,
handler:function (request,reply) {
reply(‘内容’);
}
});

访问: http://127.0.0.1/asf

 

多个路由:

server.route([{
method:’get’,
path:’/asf’,
handler:function (request,reply) {
reply.view(‘index’,{
title:’1′
});
}
},
{
method:’get’,
path:’/test’,
handler:function (request,reply) {
reply(‘继续’);
}
},
{
method:’POST’,
path:’/post_test’,
config:{
payload:{
output:’data’
}
},
handler:function (request,reply) {
reply.view(‘index’,{
title:request.payload.user
});
}
}
]);

 

可以访问:
http://127.0.0.1/asf
http://127.0.0.1/test
和提交数据到
http://127.0.0.1/post_test

 

 

18,错误页面 404、500

 

var Boom = require(‘boom’);
var Joi=require(‘joi’);

 

html: error.ejs
<p><%= statuscode %></p>
<p><%= error_name %></p>

 

 

server.route([
{
method:’get’,
path:’/asf/{name}’,
handler:function (request,reply) {
return reply(‘ok’);
},
config:{
validate:{
params:{
name:Joi.string().min(4) // 路由至少要大于等于4位
}
}
}
}
]);

 

 

server.ext(‘onPreResponse’,(request,reply)=>{
if(request.response.isBoom){
var error_name= request.response.output.payload.error;
var statuscode= request.response.output.payload.statusCode;
return reply.view(‘error’,{
statuscode:statuscode,
error_name:error_name,
title:’这里是错误页面’
}).code(statuscode);
}
reply.continue();
});

 

 

 

 

19,验证

 

hapi的内部验证推荐使用的joi模块,一旦过不了validate验证就不会执行到handler函数,直接报错Boom错误。

 

npm install joi@8.4.2 –save

var Joi=require(‘joi’); //推荐去了解更多joi用法

 

(1)get数据提交验证 (验证路径参数):

method:’get’,
path:’/asf/{id}’,
handler:function (request,reply) {
reply.view(‘index’,{
title:’sfsfsdfds’
});
},
config:{
validate:{
params:{
id:Joi.string().min(4) //必须是字符串而且要大于等于4位
}
}
}

 

(2)post数据提交验证

method:’POST’,
path:’/post_test’,
config:{
payload:{
output:’data’
},
validate:{
payload:{
user:Joi.number() // user 是 <input type=”text” name=”user” /> 值必须是数字
}
}
},

 

 

(3)服务器传给客服端时,验证数据(比较适合做对外接口的数据验证,例如采集)
method:’get’,
path:’/asf’,
handler:function (request,reply) {
reply({title:123}); //输出到客户端的对象中,其中title必须时数字
},
config:{
response:{
schema:{
title:Joi.number()
}
}
}

 

 

(4)当以get或者post方式传给后台的数据验证失败时,可以触发指定错误回调

method:’get’,
path:’/asf/{id}’,
handler:function (request,reply) {
reply.view(‘index’,{
title:’sfsfsdfds’
});
},
config:{
validate:{
params:{
id:Joi.string().min(4) //必须是字符串而且要大于等于4位
},
failAction:function (request,reply,source,error) {
reply(‘sdf’); //request,reply就是hanlder里面的
//source 告诉我们是哪部分出错
//error 输出一些错误信息
}
}
}

 

 

 

20,判断客户端是pc端还是手机端,选择加载不同的模版,域名不变

npm install hapi-mobile-detect@1.3.5 –save

 

const MobileDetect = require(‘hapi-mobile-detect’);

 

handler:function (request,reply) {

if(!!request.plugins.md.mobile()){
reply.view(‘./mobile/index_mobile’,{
title:’手机’
});
}else{
reply.view(‘index’,{
title:’pc’
});
}

}

 

./views/index.ejs
./views/mobile/index_mobile.ejs

 

 

 

21,插件(中间件)

加载需要靠:server.register加载
server.register([ //服务器启动时,仅仅加载一次,而且还是同步执行
{register:require(‘./one_module’),
options:{
xx:’参数值,传个插件’
}
}
]);

 

 

//one_module.js

exports.register=function (server,options,next) {
console.log(‘工作了’+options.xx); //工作了参数值,传个插件
next();
};

exports.register.attributes={ pkg:require(“../package.json”) };

 

如果插件依赖某个模块,可以使用:
exports.register=function (server,options,next) {
server.dependency([‘vision’,’good’],function (server,next) {
console.log(‘adsfafas’);
next();
});
next();
};

exports.register.attributes={ pkg:require(“../package.json”) };

 

 

 

 

22,服务器静态数据

var server=new Hapi.Server({
app:{
xx:’这里是自定义值’
}
});

 

 

在路由中获取:

handler:function (request,reply) {
request.server.settings.app.xx
},

 

 

 

23,缓存

 

客户端缓存:
server.route({
method:’GET’,
path:’/{param*}’,
handler:{
directory:{
path:’public’
}
},
config:{
cache:{
privacy:’private’,
expiresIn:86400*1000 //为静态资源缓存
}
}
});

 

同样也可以在路由器中配置,缓存页面。

 

 

随身笔记

hapi笔记/教程
本教程基于hapi@13.5.3 "dependencies": { "boom": "^3.2.2", "ejs": "^2.5.7", "good": "^6.6.3", "good-console": "^5.3.2", "hapi": "^13.5.3", "hapi-auth-cookie": "^6.1.1",…
扫描二维码继续阅读
2017-11-10