快递和快乐相比如何?

从 Web 应用程序设计和开发的角度来看,Express 和 Hapi 相比如何?对于基本的例子,它们看起来很相似,但是我有兴趣了解更多关于整个应用程序结构的关键差异。

例如,据我所知,Hapi 使用的 与众不同路由机制不考虑注册顺序,可以进行更快的查找,但与 Express 相比有限。还有其他重要的区别吗?

还有一个关于选择 Hapi (而不是 Express)来开发新的 npmjs.com 网站的 文章,这篇文章说: “ Hapi 的插件系统意味着我们可以隔离应用程序的不同方面和服务,以允许未来的微服务。另一方面,Express 需要更多的配置才能获得相同的功能”,它到底是什么意思?

41680 次浏览

这是一个很大的问题,需要很长的答案才能完成,所以我只讲一些最重要的区别。抱歉,这个回答还是很长。

他们有什么相似之处?

你说的完全正确:

对于基本的例子来说,它们看起来很相似

这两个框架都在解决同样的基本问题: 为在节点中构建 HTTP 服务器提供方便的 API。也就是说,比单独使用较低级的本机 http模块更方便。http模块可以做我们想做的任何事情,但是用它编写应用程序非常繁琐。

为了实现这一点,他们都使用了高级 web 框架中已经存在很长时间的概念: 路由、处理程序、插件、身份验证模块。他们的名字可能不尽相同,但大致相同。

大多数基本的例子看起来像这样:

  • 创建一条路线
  • 在请求路由时运行一个函数,准备响应
  • 回应请求

快递:

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


getSomeValue(function (obj) {


res.json({an: 'object'});
});
});

开心:

server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {


getSomeValue(function (obj) {


reply(obj);
});
}
});

这里的差别并不完全是突破性的,对吧? 那么为什么要选择其中一个而不是另一个呢?

他们有什么不同?

简单的回答是,Happy 是更多的,它做了更多的开箱即用的东西。当您只看上面的简单示例时,这可能并不清楚。事实上,这是故意的。简单的情况保持简单。因此,让我们来看看一些重大差异:

哲学

Express 的意图是非常小的。通过在 http上提供一个小的 API,只需要在上面撒上一层薄薄的灰尘,您仍然可以自己添加额外的功能。如果要读取传入请求的正文(这是一个相当常见的任务) ,则需要安装 独立模块。如果您希望将各种内容类型发送到该路由,那么还需要检查 Content-type头部,以检查它是什么,并相应地对其进行解析(例如,表单数据 vs JSON vs 多部分) ,通常使用单独的模块。

Hapi 具有丰富的特性集,通常通过配置选项公开,而不需要编写代码。例如,如果我们希望确保在处理程序运行之前,请求主体(有效负载)被完全读入内存并进行适当的解析(根据内容类型自动进行) ,那么它只是一个简单的 选择:

server.route({
config: {
payload: {
output: 'data',
parse: true
}
},
method: 'GET',
path: '/',
handler: function (request, reply) {


reply(request.payload);
}
});

特征

您只需要比较两个项目中的 API 文档,就可以看到 hapi 提供了更大的特性集。

Hapi 包含了一些 Express 所没有的内置特性(据我所知) :

可扩展性和模块化

Hapi 和 Express 以完全不同的方式处理可扩展性。有了 Express,就有了 中间件函数。中间件函数类似于过滤器,在触发处理程序之前,您将它们堆叠起来,并且所有请求都将通过它们运行。

Hapi 具有 请求生命周期并提供 延伸点,它与中间件功能相当,但是在请求生命周期中存在几个定义点。

沃尔玛创建 happy 并停止使用 Express 的原因之一是,将 Express 应用程序拆分为不同部分并让不同的团队成员安全地处理它们的块非常困难。出于这个原因,他们创建了 插件系统在哈皮。

一个插件就像一个子应用程序,你可以做任何事情在一个快乐的应用程序,添加路线,扩展点等。在一个插件中,你可以确定你没有破坏应用程序的其他部分,因为路由的注册顺序并不重要,你也不能创建冲突的路由。然后,您可以将这些插件组合到一个服务器中并进行部署。

生态系统

因为 Express 只能提供很少的开箱即用信息,所以当您需要向项目中添加任何内容时,都需要向外看。很多时候,在使用 hapi 时,您需要的特性要么是内置的,要么是由核心团队创建的模块。

最小听起来不错。但是,如果你正在构建一个严肃的生产应用程序,那么你很有可能最终会需要所有这些东西。

保安

Hapi 是由沃尔玛的团队设计的,用来管理黑色星期五的交通,所以安全和稳定一直是首要关注的问题。由于这个原因,框架做了很多额外的事情,比如限制传入的有效负载大小,以防止耗尽您的进程内存。它还提供了诸如最大事件循环延迟、使用的最大 RSS 内存和 v8堆的最大大小等选项,超过这些选项,服务器将响应503超时,而不仅仅是崩溃。

摘要

你自己评估一下。考虑一下你的需求,以及这两者中哪一个能解决你最大的问题。在两个社区(IRC,Gitter,Github)中浏览一下,看看您更喜欢哪个。别轻信我的话。黑客愉快!


免责声明: 作为 快乐之书的作者,我有偏见,以上主要是我个人的意见。

我的组织选择了哈皮,这就是我们喜欢它的原因。

哈比是:

  • 由主要兵团支持。这意味着社区的支持将是强有力的,并且在未来的发行版中始终如一。很容易找到充满激情的 Hapi 用户,而且有很多很好的教程(尽管不像 ExpressJs 教程那样数量庞大)。在这个发布日期 npm 和沃尔玛使用 Hapi。
  • 它可以促进分布式团队在后端服务的各个部分的工作,而不必对 API 表面的其余部分有全面的了解(Hapi 的插件架构就是这种质量的缩影)。
  • 让框架做框架应该做的事情: 配置事情。之后,框架应该是不可见的,并允许开发人员将他们真正的创造性精力集中在构建业务逻辑上。在使用了 Hapi 一年之后,我绝对感觉到 Hapi 完成了这个任务。我..。.开心点!

如果你想直接听到 Eran Hammer (Hapi 的领导)

在过去的四年里,哈皮已经成为许多大大小小项目的首选框架。Happy 的独特之处在于它能够扩展到大型部署和大型团队。随着项目的增长,其复杂性也在增长——工程复杂性和过程复杂性。Hapi 的体系结构和哲学处理增加的复杂性,而不需要不断重构代码 [阅读更多]

开始使用 Hapi 不会像 ExpressJs 那么容易,因为 Hapi 没有同样的“明星魅力”... ... 但是一旦你感觉舒服了,你就会获得很多里程数。作为一个不负责任地使用 ExpressJs 几年的新黑客,我花了大约2个月的时间。如果你是一个经验丰富的后端开发人员,你会知道如何阅读文档,你可能甚至不会注意到这一点。

哈皮文件可以改进的领域:

  1. 如何验证用户和创建会话
  2. 处理跨产地来源申请
  3. 上传文件(多部分,分块)

我认为身份验证将是最具挑战性的部分,因为您必须决定使用哪种类型的身份验证策略(基本身份验证、 Cookies、 JWT 令牌、 OAuth)。虽然从技术上讲,会话/身份验证环境如此支离破碎并不是 Hapi 的问题... ... 但我确实希望他们能为此提供一些帮助。这将大大提高开发者的幸福感。

剩下的两个实际上并没有那么难,只是文档可以写得稍微好一点。

关于 Hapi 或为什么 Hapi JS 的简要事实

Hapi 是以配置为中心的 它在框架中内置了身份验证和授权 它是在经受过战争考验的环境中发布的,并且已经证明了它的价值 所有的模块都有100% 的测试覆盖率 它注册了远离核心 HTTP 的最高级别的抽象 通过插件架构很容易进行可编程

在性能方面,哈皮是一个更好的选择 Hapi 使用了一种不同的路由机制,它可以执行更快的查找,并考虑注册顺序。 尽管如此,与 Express 相比,它还是相当有限的。多亏了 Hapi 插件系统,这是可能的 隔离不同的方面和服务,这些方面和服务将在未来以多种方式帮助应用程序。

用法

与 Express 相比,Hapi 是最受欢迎的框架。 Hapi 主要用于大型企业应用程序。

开发人员在创建企业应用程序时不选择 Express 的几个原因是:

用 Express 编写路线比较困难

大多数时候,中间件会阻碍您; 每次定义路由时,都必须编写尽可能多的代码。

对于希望构建 RESTful API 的开发人员来说,Hapi 是最好的选择。Hapi 具有微服务架构,还可以根据某些参数将控制从一个处理程序转移到另一个处理程序。使用 Hapi 插件,您可以享受 围绕 HTTP 的更高层次的抽象,因为您可以将业务逻辑划分为易于管理的部分。

Hapi 的另一个巨大优势是,当您配置错误时,它会提供详细的错误消息。Hapi 还允许默认配置文件上传大小。如果最大上传大小受到限制,则可以向用户发送错误消息,表明文件大小过大。这将保护您的服务器不会崩溃,因为文件上传将不再尝试缓冲整个文件。

  1. 无论使用 Express 实现什么,都可以通过 hapi.js 轻松实现。

  2. Hapi.js 非常时尚,并且很好地组织了代码。如果您看到它如何执行路由并将核心逻辑放在控制器中 你会大胆地爱上它的

  3. Hapi.js 正式提供了几个专门针对 Hapi.js 的插件,从基于令牌的授权到会话管理等等, 这是一个广告。这并不意味着不能使用传统的 npm,它们都得到了 hapi.js

  4. 的支持
  5. 如果您使用 hapi.js 编写代码,那么代码将是非常易于维护的。

再补充一点,Hapi 已经开始支持从版本16开始的“ http2”调用(如果我没记错的话)。然而,Express 在 Express 4之前还没有直接支持“ http2”模块。尽管他们已经在 Express5的 alpha 版本中发布了这个特性。

'use strict';
const Hapi = require('hapi');
const Basic = require('hapi-auth-basic');
const server = new Hapi.Server();
server.connection({
port: 2090,
host: 'localhost'
});




var vorpal = require('vorpal')();
const chalk = vorpal.chalk;
var fs = require("fs");


var utenti = [{
name: 'a',
pass: 'b'
},
{
name: 'c',
pass: 'd'
}
];


const users = {
john: {
username: 'john',
password: 'secret',
name: 'John Doe',
id: '2133d32a'
},
paul: {
username: 'paul',
password: 'password',
name: 'Paul Newman',
id: '2133d32b'
}
};


var messaggi = [{
destinazione: 'a',
sorgente: 'c',
messsaggio: 'ciao'
},
{
destinazione: 'a',
sorgente: 'c',
messsaggio: 'addio'
},
{
destinazione: 'c',
sorgente: 'a',
messsaggio: 'arrivederci'
}
];


var login = '';
var loggato = false;


vorpal
.command('login <name> <pass>')
.description('Effettua il login al sistema')
.action(function (args, callback) {
loggato = false;
utenti.forEach(element => {
if ((element.name == args.name) && (element.pass == args.pass)) {
loggato = true;
login = args.name;
console.log("Accesso effettuato");
}
});
if (!loggato)
console.log("Login e Password errati");
callback();
});


vorpal
.command('leggi')
.description('Leggi i messaggi ricevuti')
.action(function (args, callback) {
if (loggato) {
var estratti = messaggi.filter(function (element) {
return element.destinazione == login;
});


estratti.forEach(element => {
console.log("mittente : " + element.sorgente);
console.log(chalk.red(element.messsaggio));
});
} else {
console.log("Devi prima loggarti");
}
callback();
});


vorpal
.command('invia <dest> "<messaggio>"')
.description('Invia un messaggio ad un altro utente')
.action(function (args, callback) {
if (loggato) {
var trovato = utenti.find(function (element) {
return element.name == args.dest;
});
if (trovato != undefined) {
messaggi.push({
destinazione: args.dest,
sorgente: login,
messsaggio: args.messaggio
});
console.log(messaggi);
}
} else {
console.log("Devi prima loggarti");
}
callback();
});


vorpal
.command('crea <login> <pass>')
.description('Crea un nuovo utente')
.action(function (args, callback) {
var trovato = utenti.find(function (element) {
return element.name == args.login;
});
if (trovato == undefined) {
utenti.push({
name: args.login,
pass: args.pass
});
console.log(utenti);
}
callback();
});


vorpal
.command('file leggi utenti')
.description('Legge il file utenti')
.action(function (args, callback) {
var contents = fs.readFileSync("utenti.json");
utenti = JSON.parse(contents);
callback();
});


vorpal
.command('file scrivi utenti')
.description('Scrive il file utenti')
.action(function (args, callback) {
var jsontostring = JSON.stringify(utenti);
fs.writeFile('utenti.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
callback();
});


vorpal
.command('file leggi messaggi')
.description('Legge il file messaggi')
.action(function (args, callback) {
var contents = fs.readFileSync("messaggi.json");
messaggi = JSON.parse(contents);
callback();
});


vorpal
.command('file scrivi messaggi')
.description('Scrive il file messaggi')
.action(function (args, callback) {
var jsontostring = JSON.stringify(messaggi);
fs.writeFile('messaggi.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
callback();
});


// leggi file , scrivi file


vorpal
.delimiter(chalk.yellow('messaggi$'))
.show();








const validate = function (request, username, password, callback) {
loggato = false;




utenti.forEach(element => {
if ((element.name == username) && (element.pass == password)) {
loggato = true;
console.log("Accesso effettuato");
return callback(null, true, {
name: username
})
}
});
if (!loggato)
return callback(null, false);
};


server.register(Basic, function (err) {
if (err) {
throw err;
}
});


server.auth.strategy('simple', 'basic', {
validateFunc: validate
});






server.route({
method: 'GET',
path: '/',
config: {
auth: 'simple',
handler: function (request, reply) {
reply('hello, ' + request.auth.credentials.name);
}
}
});


//route scrivere
server.route({
method: 'POST',
path: '/invia',
config: {
auth: 'simple',
handler: function (request, reply) {
//console.log("Received POST from " + request.payload.name + "; id=" + (request.payload.id || 'anon'));
var payload = encodeURIComponent(request.payload)
console.log(request.payload);
console.log(request.payload.dest);
console.log(request.payload.messaggio);
messaggi.push({
destinazione: request.payload.dest,
sorgente: request.auth.credentials.name,
messsaggio: request.payload.messaggio
});
var jsontostring = JSON.stringify(messaggi);
fs.writeFile('messaggi.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
console.log(messaggi);
reply(messaggi[messaggi.length - 1]);


}
}
});




//route leggere (json)
server.route({
method: 'GET',
path: '/messaggi',
config: {
auth: 'simple',
handler: function (request, reply) {
messaggi = fs.readFileSync("messaggi.json");
var estratti = messaggi.filter(function (element) {
return element.destinazione == request.auth.credentials.name;
});
var s = [];


console.log(request.auth.credentials.name);
console.log(estratti.length);
estratti.forEach(element => {


s.push(element);


//fare l'array con stringify
//s+="mittente : "+element.sorgente+": "+element.messsaggio+"\n";


});
var a = JSON.stringify(s);
console.log(a);
console.log(s);
reply(a);
}
}
});






server.start(function () {
console.log('Hapi is listening to ' + server.info.uri);
});


function EseguiSql(connection, sql, reply) {
var rows = [];
request = new Request(sql, function (err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
console.log("Invio Reply")
reply(rows);
}
});


request.on('row', function (columns) {
var row = {};
columns.forEach(function (column) {
row[column.metadata.colName] = column.value;
});
rows.push(row);
});


connection.execSql(request);
}


server.route({
method: 'POST',
path: '/query',
handler: function (request, reply) {
// Qui dovrebbe cercare i dati nel body e rispondere con la query eseguita
var connection = new Connection(config);


// Attempt to connect and execute queries if connection goes through
connection.on('connect', function (err) {
if (err) {
console.log(err);
} else {


console.log('Connected');
console.log(request.payload.sql);
EseguiSql(connection, request.payload.sql, reply);
}
});


}
});


server.connection({
host: process.env.HOST || 'localhost',
port: process.env.PORT || 8080
});


var config = {
userName: process.env.DB_USER,
password: process.env.DB_PASSWORD,
server: process.env.DB_SERVER,
options: {
database: process.env.DB_NAME,
encrypt: true
}
}

我最近开始使用 Hapi,我对它很满意。我的理由是

  1. 更容易测试,例如:

    • server.inject 允许您运行应用程序并获得响应,而无需运行和监听。
    • server.info 给出当前的 uri、端口等。
    • server.settings 访问配置,例如 server.settings.cache获取当前的缓存提供程序
    • 当有疑问时,查看 /test文件夹中应用程序或支持的插件的任何部分,看看如何模拟/测试/存根等建议。
    • 我的感觉是,hapi 的架构模型允许你信任,但验证,例如,我的 已注册的插件?我如何申报 模块依赖性
  2. 它可以自由工作,例如 文件上传,从端点返回流等等。

  3. 必要的插件和核心库一起维护。例如 模板解析缓存等。额外的好处是,相同的编码标准适用于基本的事情。

  4. 正常的错误和错误处理。并保持一个内部路由表,以防止重复的路由。这在学习过程中非常有用,因为错误是在早期抛出的,而不是需要调试的意外行为。