Як Експрес і Хапі порівнюють один одного?


133

З точки зору дизайну та розробки веб-додатків, як Експрес та Хапі порівнюють один одного? Для основних прикладів вони здаються схожими, проте мені цікаво дізнатися більше про ключові відмінності в загальній структурі додатків.

Наприклад, наскільки я дізнався, Hapi використовує інший механізм маршрутизації, який не враховує порядок реєстрації, може робити швидші пошуки, але порівняно з Express. Чи є інші важливі відмінності?

Також є стаття про вибір Hapi (over Express) для розробки нового веб-сайту npmjs.com. У цій статті йдеться про те, що "Система плагінів Hapi означає, що ми можемо ізолювати різні грані та сервіси програми так, що дозволить забезпечити мікросервіси в майбутнє. Експрес, з іншого боку, вимагає трохи більшої конфігурації, щоб отримати той самий функціонал ", що це точно означає?

Відповіді:


231

Це велике питання і вимагає, щоб довга відповідь була повною, тому я просто вирішу підмножину найважливіших відмінностей. Вибачте, що це все ще тривала відповідь.

Чим вони схожі?

Ти абсолютно прав, коли кажеш:

Для основних прикладів вони здаються схожими

Обидва рамки вирішують одну і ту ж основну проблему: Надання зручного API для побудови HTTP-серверів у вузлі. Тобто, зручніше, ніж використання лише рідного httpмодуля нижчого рівня . httpМодуль може зробити все , що ми хочемо , але це втомлює для додатків записи с.

Щоб досягти цього, вони обидва використовують концепції, які вже давно існують у веб-рамках високого рівня: маршрутизація, обробники, плагіни, модулі аутентифікації. Вони, можливо, не завжди мали однакові імена, але вони приблизно рівнозначні.

Більшість основних прикладів виглядають приблизно так:

  • Створіть маршрут
  • Запустіть функцію, коли запитується маршрут, готуючи відповідь
  • Відповідь на запит

Експрес:

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);
        });
    }
});

Різниця тут не зовсім новаторська? То чому б обирати одне над іншим?

Чим вони відрізняються?

Проста відповідь: хапі - це набагато більше, і це робить набагато більше поза коробкою. Це може бути не зрозуміло, коли ви просто дивитесь на простий приклад зверху. Насправді це навмисно. Прості справи зберігаються простими. Тож давайте вивчимо деякі великі відмінності:

Філософія

Експрес призначений бути дуже мінімальним. Надаючи вам невеликий API з тонким запиленням поверх http, ви все ще дуже сильно ставитеся до того, щоб додати додаткову функціональність. Якщо ви хочете прочитати тіло вхідного запиту (досить поширене завдання), вам потрібно встановити окремий модуль . Якщо ви очікуєте, що різні типи вмісту будуть надсилатися на цей маршрут, вам також потрібно перевірити Content-typeзаголовок, щоб перевірити, що це таке, і проаналізувати його відповідно (наприклад, форма даних проти JSON проти багаточастинності, наприклад), часто використовуючи окремі модулі .

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 має життєвий цикл запиту і пропонує точки розширення , які можна порівняти з функціями проміжного програмного забезпечення, але існують кілька визначених точок у життєвому циклі запиту.

Однією з причин того, що Walmart побудував hapi і перестав використовувати Express, було розчарування тим, як важко було розділити додаток Express на окремі частини, а різні члени команди працюють безпечно над їхньою частиною. З цієї причини вони створили систему плагінів у хапі.

Плагін - це як додаткове додаток, ви можете робити все, що можна, у додатку hapi, додавати маршрути, точки розширення тощо. У плагіні ви можете бути впевнені, що ви не порушуєте іншу частину програми, оскільки порядок реєстрація маршрутів не має значення, і ви не можете створювати конфліктні маршрути. Потім можна поєднати ці плагіни на сервері та розгорнути їх.

Екосистема

Оскільки Експрес дає вам так мало поза коробкою, вам потрібно шукати зовні, коли вам потрібно щось додати до свого проекту. Багато разів працюючи з хапі, функція, яка вам потрібна, є або вбудованою, або є модуль, створений основною командою.

Мінімально звучить чудово. Але якщо ви будуєте серйозну програму для виробництва, швидше за все, вам знадобляться всі ці речі з часом.

Безпека

hapi була розроблена командою в Walmart для управління трафіком Чорної п’ятниці, тому безпека та стабільність завжди були головним питанням. З цієї причини рамка робить багато чого іншого, наприклад обмеження розміру вхідної корисної навантаження, щоб запобігти виснаженню пам’яті вашого процесу. Він також має такі параметри, як максимальна затримка циклу подій, максимальна використовувана пам'ять RSS та максимальний розмір купи v8, поза яким ваш сервер реагуватиме на час очікування 503, а не просто на збої.

Підсумок

Оцініть їх обох самостійно. Подумайте про свої потреби та про те, яке з двох вирішує Ваші найбільші проблеми. Пориньте у дві спільноти (IRC, Gitter, Github), подивіться, що вам більше подобається. Не прийміть мого слова. І щасливе хакерство!


ВІДПОВІДАЛЬНІСТЬ: Я упереджений як автор книги про хапі, і вищезгадане - це в основному моя особиста думка.


7
Метт, дякую за широкий пост, розділи "розширюваність та модульність" та "безпека" були найбільш корисними розділами для мене. Я думаю, варто згадати, що нова система маршрутизації в Express 4 забезпечує покращену модульність для додатків.
Алі Шакіба

1
Чудова відповідь Метт. Нас також плутають ч / б Hapi та Express, один недолік, який ми спостерігаємо з Hapi, - це те, що він не має підтримки громади настільки широкий, як Express, і це може стати головною проблемою, якщо ми десь застрягнемо. Потрібна ваша думка щодо того ж.
Аман Гупта

1
Експрес є загальним, тоді як хапі - це трохи більше підприємство.
windmaomao

1
@MattHarrison чудова відповідь, зараз я читаю вашу книгу про Хапі, вона просто чудова. Я збираюся розробити нову ринок книг, що використовують Hapi в бекенді та vue.js на кордоні, після звикання до Хапі я хотів би активно брати участь у проекті Хапі.
Гумоюн Ахмад

1
@Humoyun Чудово! Будьте в курсі, що існує нова основна версія хапі з деякими істотними змінами після <= v16.0.0. Зараз я створюю серіал екранізації, призначений для того, щоб люди дізналися v17: youtube.com/playlist?list=PLi303AVTbxaxqjaSWPg94nccYIfqNoCHz
Метт Харрісон

54

Моя організація працює з Хапі. Ось чому нам це подобається.

Хапі:

  • Підкріплений великими корпусами. Це означає, що підтримка спільноти буде сильною, і вона буде вам надалі протягом майбутніх версій. Знайти пристрасних людей Хапі, і там є хороші підручники (хоча і не такі численні та поширені, як підручники ExpressJs). Починаючи з цієї дати публікації npm та Walmart використовують Hapi.
  • Це може полегшити роботу розподілених команд, що працюють над різними частинами допоміжних сервісів, не маючи всебічних знань про іншу поверхню API (архітектура плагінів Хапі є втіленням цієї якості).
  • Дозвольте фреймворку зробити те, що повинен бути: налаштування речей. Після цього рамки повинні бути невидимими і дозволяти розробникам зосередити свою справжню творчу енергію на розробці бізнес-логіки. Після використання Хапі протягом року, я безумовно відчуваю, що Хапі досягає цього. Я відчуваю себе щасливим!

Якщо ви хочете почути безпосередньо від Ерана Хаммера (ведучий Хапі)

Протягом останніх чотирьох років хапі зростав основою вибору для багатьох проектів, великих чи малих. Хапі унікальним є його здатність масштабувати великі розміщення та великі команди. З ростом проекту зростає і його складність - інженерна складність і складність процесу. Архітектура та філософія хапі справляється з підвищеною складністю без необхідності постійно переробляти код [читати далі]

Початок роботи з Hapi не буде таким простим, як ExpressJs, тому що Hapi не має такої ж "зіркової сили" ... але як тільки ви відчуєте себе комфортно, ви отримаєте багато пробігу. Приймав мене близько 2 місяців як нового хакера, який безвідповідально використовував ExpressJs протягом декількох років. Якщо ви досвідчений розробник бекенда, ви знаєте, як читати документи, і ви, мабуть, цього навіть не помітите.

Області, в яких документація Hapi може покращитися:

  1. як аутентифікувати користувачів та створити сеанси
  2. обробка запитів на перехресне походження (CORS)
  3. завантаження файлів (багаточастинні, зрізані)

Я думаю, що автентифікація була б найскладнішою її частиною, тому що ви повинні визначитися з якою стратегією автентичного використання (Basic Authentication, Cookies, JWT Tokens, OAuth). Хоча технічно це не проблема Хапі в тому, що ландшафт сеансів / аутентифікації настільки фрагментарний ... але я б хотів, щоб вони надали певну руку для цього. Це значно збільшило б щастя розробника.

Решта два насправді не такі складні, документи можна просто написати трохи краще.


3

Швидкі факти про Hapi Або чому Hapi JS?

Хапі орієнтований на конфігурацію. У ньому є вбудована автентифікація та авторизація. Він був випущений в атмосфері, перевіреній боєм, і справді довів свою цінність. Усі модулі мають 100% тестове покриття. Він реєструє найвищий рівень абстрагування від основного HTTP. через архітектуру плагінів

Хапі - це кращий вибір продуктивності. Хапі використовує інший механізм маршрутизації, який може робити швидші пошуки та враховувати порядок реєстрації. Тим не менше, він порівняно з Експресом досить обмежений. Завдяки системі плагінів Hapi можна виділити різні грані та сервіси, які допоможуть додатку багатьма способами в майбутньому.

Використання

Хапі - це найбільш переважний фреймворк порівняно з Express. Hapi використовується в основному для масштабних корпоративних програм.

Кілька причин, чому розробники не вибирають Express при створенні корпоративних програм, є:

Маршрути складніше скласти в Express

Посереднє програмне забезпечення заважає більшість часу; щоразу, коли ви визначаєте маршрути, ви повинні написати стільки номерів кодів.

Hapi був би кращим вибором для розробника, який прагне створити API RESTful. У Hapi є архітектура мікро-сервісу, а також можливо передати управління з одного обробника на інший на основі певних параметрів. Завдяки плагіну Hapi ви можете насолоджуватися більш високим рівнем абстрагування навколо HTTP, оскільки ви можете розширити ділову логіку на деталі, легко керовані.

Ще одна величезна перевага компанії Hapi полягає в тому, що він надає детальні повідомлення про помилки при неправильній налаштуваннях. Hapi також дозволяє налаштувати розмір завантаження файлу за замовчуванням. Якщо максимальний розмір завантаження обмежений, ви можете надіслати повідомлення про помилку, повідомляючи про те, що розмір файлу занадто великий. Це захистить ваш сервер від збоїв, оскільки завантаження файлів більше не намагатиметься зберігати весь файл.

  1. Все, що ви можете досягти, використовуючи експрес, також можна легко досягти, використовуючи hapi.js.

  2. Hapi.js дуже стильний і дуже добре організовує код. Якщо ви бачите, як він здійснює маршрутизацію, і вводить основну логіку в контролери, ви будете викликати захоплення.

  3. Hapi.js офіційно надає декілька плагінів виключно для діапазонів hapi.js від аутентифікації на основі токена до керування сесіями та багато іншого, що є рекламою. Це не означає, що традиційні npm не можна використовувати, всі вони підтримуються hapi.js

  4. Якщо ви введете код у hapi.js, код буде дуже ретельним.


"Якщо ви бачите, як це відбувається маршрутизація і вводить основну логіку в контролери ...". Я не бачу прикладу в документації, яка показує використання контролерів. У всіх прикладах маршрутизації використовується властивість обробника, яка є функцією. Я порівнюю цей спосіб з тим, що роблять Laravel (PHP Framework) і AdonisJs (Node.js Framework) для маршрутизації, в якій ми можемо використовувати контролери для маршрутизації. Я, мабуть, пропустив частини документа HAPI, які показують використання контролерів для маршрутизації. Тож якщо ця функція існує, мені буде добре, тому що я звик використовувати контролери для маршрутизації в Laravel.
Lex Soft

1

Я почав користуватися Hapi останнім часом і цілком задоволений цим. Мої причини такі

  1. Простіше тестувати. Наприклад:

    • server.inject дозволяє запустити додаток та отримати відповідь, не запускаючи його та слухаючи.
    • server.info дає поточний uri, порт тощо.
    • server.settingsдоступ до конфігурації, наприклад, server.settings.cacheотримує поточний постачальник кеш-пам'яті
    • коли ви сумніваєтесь, перегляньте /testпапки будь-якої частини програми або підтримуваних плагінів, щоб побачити пропозиції щодо макетування / тестування / заглушки тощо.
    • моє відчуття полягає в тому, що архітектурна модель хапі дозволяє вам довіряти, але перевіряти, наприклад, чи зареєстровані мої плагіни ? Як я можу оголосити модульну залежність ?
  2. Це працює з поля, наприклад, завантаження файлів , повернення потоків з кінцевих точок тощо.

  3. Основні плагіни підтримуються разом з основною бібліотекою. наприклад, розбір шаблонів , кешування тощо. Додатковою перевагою є ті самі стандарти кодування, що застосовуються в усіх основних речах.

  4. Зрозумілі помилки та керування помилками. Хапі перевіряє параметри конфігурації та зберігає внутрішню таблицю маршрутів, щоб запобігти повторенню маршрутів. Це дуже корисно під час навчання, оскільки помилки викидаються рано замість несподіваних поведінок, які потребують налагодження.


-1

Ще один момент, який потрібно додати, Hapi почав підтримувати дзвінки http2 від версії 16 (якщо я не помиляюся). Однак Express ще не підтримує модуль 'http2' безпосередньо до Express 4. Хоча вони випустили функцію в альфа-версії Express 5.


-2
'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
    }
}

Ласкаво просимо до StackOverflow. Чи могли б ви детальніше розглянути свою відповідь та як вона стосується питання, поставленого ОП?
Szymon Maszke
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.