To duże pytanie, które wymaga długiej odpowiedzi, aby było kompletne, dlatego omówię tylko podzbiór najważniejszych różnic. Przepraszamy, że to wciąż długa odpowiedź.
Jak są podobne?
Masz całkowitą rację, mówiąc:
W przypadku podstawowych przykładów wydają się podobne
Obie struktury rozwiązują ten sam podstawowy problem: zapewnienie wygodnego interfejsu API do budowania serwerów HTTP w węźle. Oznacza to, że jest to wygodniejsze niż używanie http
samego modułu natywnego niższego poziomu . http
Moduł może zrobić wszystko, co chcemy, ale jest to uciążliwe do zastosowań pisać.
Aby to osiągnąć, obaj używają koncepcji, które od dawna są obecne w strukturach internetowych wysokiego poziomu: routing, moduły obsługi, wtyczki, moduły uwierzytelniające. Może nie zawsze miały te same nazwy, ale są z grubsza równoważne.
Większość podstawowych przykładów wygląda mniej więcej tak:
- Utwórz trasę
- Uruchom funkcję, gdy żądana jest trasa, przygotowując odpowiedź
- Odpowiedz na żądanie
Wyrazić:
app.get('/', function (req, res) {
getSomeValue(function (obj) {
res.json({an: 'object'});
});
});
hapi:
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
getSomeValue(function (obj) {
reply(obj);
});
}
});
Różnica nie jest tutaj przełomowa, prawda? Po co więc wybierać jedną z nich?
Czym się różnią?
Prosta odpowiedź brzmi: hapi to o wiele więcej i robi o wiele więcej po wyjęciu z pudełka. To może nie być jasne, jeśli spojrzysz na prosty przykład z góry. W rzeczywistości jest to zamierzone. Proste przypadki są proste. Przyjrzyjmy się więc niektórym z dużych różnic:
Filozofia
Express ma być bardzo minimalny. Dając ci małe API z niewielkim odkurzaniem http
, nadal jesteś sam, jeśli chodzi o dodawanie dodatkowych funkcji. Jeśli chcesz odczytać treść przychodzącego żądania (dość powszechne zadanie), musisz zainstalować osobny moduł . Jeśli spodziewasz się wysłania różnych typów zawartości do tej trasy, musisz również sprawdzić Content-type
nagłówek, aby sprawdzić, który to jest i odpowiednio go przeanalizować (na przykład dane formularza, JSON lub wiele części), często używając oddzielnych modułów .
hapi ma bogaty zestaw funkcji, często ujawnianych za pomocą opcji konfiguracyjnych, zamiast wymagać pisania kodu. Na przykład, jeśli chcemy się upewnić, że treść żądania (ładunek) jest w pełni wczytywana do pamięci i odpowiednio analizowana (automatycznie na podstawie typu zawartości) przed uruchomieniem procedury obsługi, jest to tylko prosta opcja :
server.route({
config: {
payload: {
output: 'data',
parse: true
}
},
method: 'GET',
path: '/',
handler: function (request, reply) {
reply(request.payload);
}
});
cechy
Wystarczy porównać dokumentację API w obu projektach, aby przekonać się, że hapi oferuje większy zestaw funkcji.
hapi zawiera niektóre z następujących wbudowanych funkcji, których Express nie ma (o ile wiem):
Rozszerzalność i modułowość
hapi i Express podchodzą do rozszerzalności w zupełnie inny sposób. Dzięki Express masz funkcje oprogramowania pośredniego . Funkcje oprogramowania pośredniego są podobne do filtrów, które układasz w stos i wszystkie żądania przechodzą przez nie przed trafieniem do programu obsługi.
hapi ma cykl życia żądania i oferuje punkty rozszerzeń , które są porównywalne z funkcjami oprogramowania pośredniego, ale istnieją kilka zdefiniowanych punktów w cyklu życia żądania.
Jednym z powodów, dla których Walmart stworzył hapi i przestał używać Express, była frustracja z powodu tego, jak trudno było podzielić aplikację Express na oddzielne części i pozwolić, aby różni członkowie zespołu bezpiecznie pracowali nad swoim fragmentem. Z tego powodu stworzyli system wtyczek w hapi.
Wtyczka jest jak podaplikacja, możesz zrobić wszystko, co możesz w aplikacji hapi, dodawać trasy, punkty rozszerzeń itp. We wtyczce masz pewność, że nie psujesz innej części aplikacji, ponieważ kolejność rejestracje tras nie mają znaczenia i nie można tworzyć tras kolidujących. Następnie możesz połączyć te wtyczki na serwerze i wdrożyć go.
Ekosystem
Ponieważ Express daje tak niewiele po wyjęciu z pudełka, musisz wyglądać na zewnątrz, gdy chcesz coś dodać do swojego projektu. Często podczas pracy z hapi funkcja, której potrzebujesz, jest albo wbudowana, albo istnieje moduł utworzony przez podstawowy zespół.
Minimalne brzmi świetnie. Ale jeśli tworzysz poważną aplikację produkcyjną, prawdopodobnie będziesz potrzebować wszystkich tych rzeczy.
Bezpieczeństwo
hapi został zaprojektowany przez zespół Walmart do obsługi ruchu w Czarny piątek, więc bezpieczeństwo i stabilność zawsze były głównym problemem. Z tego powodu framework robi wiele dodatkowych rzeczy, takich jak ograniczanie rozmiaru przychodzącego ładunku, aby zapobiec wyczerpaniu pamięci procesu. Ma również opcje dotyczące takich rzeczy, jak maksymalne opóźnienie pętli zdarzeń, maksymalna używana pamięć RSS i maksymalny rozmiar sterty v8, po przekroczeniu którego serwer odpowie z limitem czasu 503 zamiast po prostu się zawiesić.
Podsumowanie
Oceń ich obu samodzielnie. Pomyśl o swoich potrzebach i o tym, która z nich jest odpowiedzią na Twoje największe obawy. Zanurz się w dwóch społecznościach (IRC, Gitter, Github), zobacz, które wolisz. Nie wierz mi na słowo. I miłego hakowania!
ZRZECZENIE SIĘ: Jestem stronniczy jako autor książki o hapi, a powyższe jest w dużej mierze moją osobistą opinią.
Moja organizacja korzysta z Hapi. Dlatego to lubimy.
Hapi to:
Jeśli chcesz usłyszeć bezpośrednio od Erana Hammera (prowadzącego Hapi)
Rozpoczęcie pracy z Hapi nie będzie tak łatwe, jak ExpressJs, ponieważ Hapi nie ma takiej samej „mocy gwiazdowej”… ale kiedy poczujesz się komfortowo, będziesz mieć DUŻO kilometrów. Zajęło mi to około 2 miesiące jako nowy haker, który nieodpowiedzialnie używał ExpressJs przez kilka lat. Jeśli jesteś doświadczonym programistą backendu, będziesz wiedział, jak czytać dokumenty i prawdopodobnie nawet tego nie zauważysz.
Obszary, w których dokumentacja Hapi może zostać ulepszona:
Myślę, że uwierzytelnianie byłoby najtrudniejszą częścią tego procesu, ponieważ musisz zdecydować, jakiej strategii uwierzytelniania użyć (uwierzytelnianie podstawowe, pliki cookie, tokeny JWT, OAuth). Chociaż technicznie nie jest to problem Hapi, że krajobraz sesji / uwierzytelniania jest tak podzielony ... ale chciałbym, aby zapewnili do tego trochę ręki. To znacznie zwiększyłoby zadowolenie deweloperów.
Pozostałe dwa nie są w rzeczywistości takie trudne, dokumenty mogłyby być napisane trochę lepiej.
źródło
Szybkie fakty o Hapi lub dlaczego Hapi JS?
Hapi jest zorientowany na konfigurację Ma wbudowane uwierzytelnianie i autoryzację Został wydany w atmosferze testowanej w boju i naprawdę udowodnił swoją wartość Wszystkie moduły mają 100% pokrycie testowe Rejestruje najwyższy poziom abstrakcji z dala od rdzenia HTTP Łatwo porównywalny poprzez architekturę wtyczek
Hapi to lepszy wybór, jeśli chodzi o wydajność. Hapi używa innego mechanizmu routingu, który może wykonywać szybsze wyszukiwania i uwzględniać kolejność rejestracji. Niemniej jednak jest dość ograniczony w porównaniu z Expressem. Dzięki systemowi wtyczek Hapi możliwe jest wyodrębnienie różnych aspektów i usług, które w przyszłości na wiele sposobów pomogłyby aplikacji.
Stosowanie
Hapi jest najbardziej preferowanym frameworkiem w porównaniu do Express. Hapi jest używany głównie w dużych aplikacjach korporacyjnych.
Oto kilka powodów, dla których programiści nie wybierają Express podczas tworzenia aplikacji dla przedsiębiorstw:
Trasy są trudniejsze do utworzenia w Express
Oprogramowanie pośredniczące często przeszkadza; za każdym razem, gdy definiujesz trasy, musisz wpisać tyle numerów kodów.
Hapi byłby najlepszym wyborem dla programisty, który chce zbudować RESTful API. Hapi ma architekturę mikroserwisów i możliwe jest również przeniesienie kontroli z jednej obsługi na drugą w oparciu o określone parametry. Dzięki wtyczce Hapi możesz cieszyć się wyższym poziomem abstrakcji wokół HTTP, ponieważ możesz podzielić logikę biznesową na części, które można łatwo zarządzać.
Kolejną ogromną zaletą Hapi jest to, że wyświetla szczegółowe komunikaty o błędach w przypadku błędnej konfiguracji. Hapi pozwala również domyślnie skonfigurować rozmiar wysyłanych plików. Jeśli maksymalny rozmiar wysyłania jest ograniczony, możesz wysłać do użytkownika komunikat o błędzie informujący, że rozmiar pliku jest zbyt duży. To ochroniłoby twój serwer przed awarią, ponieważ przesyłane pliki nie będą już próbować buforować całego pliku.
Cokolwiek możesz osiągnąć używając ekspresu, możesz też łatwo osiągnąć używając hapi.js.
Hapi.js jest bardzo stylowy i bardzo dobrze organizuje kod. Jeśli zobaczysz, jak działa routing i umieszcza podstawową logikę w kontrolerach, z pewnością to pokochasz.
Hapi.js oficjalnie zapewnia kilka wtyczek wyłącznie dla hapi.js, od uwierzytelniania opartego na tokenach po zarządzanie sesjami i wiele innych, czyli reklamy. Nie oznacza to, że nie można użyć tradycyjnego npm, wszystkie są obsługiwane przez hapi.js
Jeśli tworzysz kod w hapi.js, kod byłby bardzo łatwy w utrzymaniu.
źródło
Niedawno zacząłem używać Hapi i jestem z niego całkiem zadowolony. Moje powody są
Łatwiejsze do przetestowania. Na przykład:
server.inject
umożliwia uruchomienie aplikacji i uzyskanie odpowiedzi bez jej uruchamiania i nasłuchiwania.server.info
podaje aktualny URI, port itp.server.settings
uzyskuje dostęp do konfiguracji, np.server.settings.cache
pobiera aktualnego dostawcę pamięci podręcznej/test
foldery dowolnej części aplikacji lub obsługiwanych wtyczek, aby zobaczyć sugestie, jak mock / test / stub itp.Działa po wyjęciu z pudełka, np. Przesyłanie plików , strumienie zwrotne z punktów końcowych itp.
Podstawowe wtyczki są utrzymywane wraz z podstawową biblioteką. np. parsowanie szablonów , buforowanie itp. Dodatkową korzyścią jest to, że te same standardy kodowania są stosowane w najważniejszych rzeczach.
Rozsądne błędy i obsługa błędów. Hapi sprawdza poprawność opcji konfiguracyjnych i przechowuje wewnętrzną tabelę tras, aby zapobiec powielaniu tras. Jest to bardzo przydatne podczas nauki, ponieważ błędy są generowane wcześnie, zamiast nieoczekiwanych zachowań, które wymagają debugowania.
źródło
Jeszcze jedna kwestia do dodania, Hapi zaczął obsługiwać wywołania „http2” począwszy od wersji 16 (jeśli się nie mylę). Jednak express nie obsługuje jeszcze modułu „http2” bezpośrednio do express 4. Chociaż wydali tę funkcję w wersji alfa programu express 5.
źródło
'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 } }
źródło