Node.js: una panoramica sul Java Runtime Environment

Se si usano linguaggi di programmazione lato server come C o PHP, il codice di programmazione di regola viene portato a termine in maniera sequenziale. Questo significa che un server inizia con la configurazione di una nuova indicazione nel codice solo quando queste sono state eseguite e si è ottenuto un risultato. In questo caso si parla di un’elaborazione asincrona. L’esecuzione di ulteriori codici viene bloccata fino a quando il processo attuale non viene terminato. Questo può portare a consistenti ritardi nelle azioni che richiedono più tempo come accessi al file system, a database o a servizi web.

Molti linguaggi di programmazione, i runtime environment e le implementazioni basate su questi, supportano quindi la possibilità di eseguire parallelamente processi nei cosiddetti thread. Si tratta di singoli thread di esecuzione nell’ambito di un processo, con i quali si portano a termine le azioni, mentre viene eseguito il resto del codice. Lo svantaggio di questo metodo consiste nel fatto che più thread vengono iniziati, più tempo di CPU e memoria RAM sono necessari. In altre parole: il multithreading richiede molte risorse. Inoltre, ulteriori thread sono associati a sforzi di programmazione decisamente maggiori. Con un’implementazione lato server di JavaScript è possibile aggirare questi problemi. La base per questo è messa a disposizione da Node.js.

Che cos’è Node.js?

Node.js è una piattaforma software con un’architettura basata su eventi, che permette di usare lato server il linguaggio di scripting JavaScript, originariamente pensato per l’utilizzo lato client. Questo significa quindi che si possono usare i linguaggi Java, .NET, Ruby o Pyton allo stesso modo di PHP per scrivere il codice per il server. Node.js si usa nello sviluppo di applicazioni JavaScript lato server, che devono elaborare in tempo reale una grande quantità di dati. Il runtime enviroment è molto popolare per realizzare un web server leggero.  

Il software multipiattaforma fu creato nel 2009 da Ryan Dahl ed era basato su V8, il motore JavaScript di Google, utilizzato anche nel browser Chrome. Iniziato dall’azienda Joyent, il progetto nella forma di Node.js è passato dal 2005 alla Linux Foundation. Versioni aggiornate sono disponibili per Windows, Mac OS e Linux.

Node.js ha una libreria di diversi moduli JavaScript, che possono essere caricati attraverso una funzione semplice e messi a disposizione come componenti già pronti per lo sviluppo delle applicazioni web. Un esempio è il modulo http, che permette di generare con una singola funzione un web server obsoleto. Inoltre è possibile installare moduli aggiuntivi con il sistema di gestione dei pacchetti NPM (Node Package Manager).

Un grande vantaggio di Node.js è l’architettura basata su eventi, con la quale è possibile eseguire il codice di programmazione in maniera asincrona. Pertanto Node.js si affida su thread singoli ed un sistema di input-output (I/O) esterno, che permette un’elaborazione parallela di più operazioni di scrittura e lettura.

  • I/O asincrono: tra i compiti classici di un server rientrano il rispondere alle richieste, il salvataggio dei dati in un database, la lettura dei file da un disco rigido e l’instaurazione di collegamenti tra le altre componenti di rete. Queste attività vengono raggruppate sotto l’acronimo I/O (input/output). Nei linguaggi di programmazione, come C o Java, le operazioni I/O vengono eseguite in modo sincrono, portando a termine un compito dopo l’altro. Pertanto il sistema di input-output si blocca fino a che il compito attuale non viene portato a termine. Node.js usa invece un I/O asincrono, nel quale vengono delegate le operazioni di scrittura e di lettura al sistema operativo o al database. Questo permette di eseguire un grande numero di operazioni I/O parallelamente, senza che si arrivi al blocking, cosa che procura alle applicazioni basate su Node.js e JavaScript un grande vantaggio in termini di velocità.  
  • Thread singoli: per compensare i tempi di attesa nell’I/O sincrono, le applicazioni server sulla base del linguaggio di programmazione classico lato server usano ulteriori thread, con gli svantaggi sopra accennati di un approccio multithreading. Così ad esempio un Apache HTTP Server inizia un nuovo thread per ogni richiesta in entrata. Il numero dei possibili thread viene limitato dalla memoria di lavoro a disposizione e con questo anche il numero delle richieste, a cui si può rispondere parallelamente in un sistema multithreading sincrono. Node.js invece utilizza un solo thread sulla base del sistema di input-output esterno, e in questo modo sia la complessità che l’utilizzo delle risorse vengono chiaramente ridotte.
  • Architettura basata su eventi: l’elaborazione asincrona delle operazioni I/O si realizza attraverso la struttura basata su eventi di Node.js. Questa si basa sostanzialmente su un singolo thread, che si trova in un event loop infinito. Questo event loop ha il compito di aspettare gli eventi e di gestirli. Intanto gli eventi possono essere presentati sia come compiti che come risultati. Se l’event loop registra un compito, ad esempio una richiesta del database, la si salva nel background attraverso la funzione callback di un processo. L’elaborazione del compito non avviene quindi nello stesso thread, nel quale si esegue l’event loop, in modo che questo possa passare immediatamente all’evento successivo. Se si è eseguito un compito esterno, i risultati del processo esterno vengono restituiti attraverso la funzione callback come nuovi risultati dell’event loop. Di conseguenza questo può influire sulla consegna dei risultati.

Il modello basato su eventi e senza blocking ha il vantaggio che un’applicazione basata su Node.js non aspetti mai inerte i risultati. Così è ad esempio possibile eseguire allo stesso tempo diverse richieste del database, senza che l’esecuzione del programma venga arrestata. Una struttura di un sito web, che necessita di diverse richieste esterne, si può quindi effettuare con Node.js in maniera molto più veloce rispetto ad un processo di elaborazione sincrono.

Consiglio

Anche il nostro articolo "Deno: il runtime per JavaScript e TypeScript" potrebbe interessarvi.

Installazione

Node.js si può scaricare sul sito web ufficiale del software, a seconda del sistema operativo, come Installer e/o pacchetto binario. Inoltre Node.js mette a disposizione, oltre al codice sorgente del software, anche il pacchetto binario per ARM, SunOS e Docker Image.

Ad esempio, per installare Node.js su di un computer Windows, si deve solo scaricare Windows Installer, scegliendo tra la versione 32 –bit o quella 64-bit. Oltre alla versione attuale 6.9.2 avete a disposizione il LTS-Release 4.4.7 (Long-term-Support), che è anche la base di questo tutorial. Con un doppio click sul file .msi si apre il set up di installazione. Qui gli utenti possono cliccare sulle clausole del contratto di licenza, accettarle e scegliere il path di installazione e i componenti da installare.

Potete verificare se l’installazione è avvenuta con successo riavviando Node.js ed eseguendo il primo codice JavaScript. Per questo è adatta la funzione console.log(), con la quale inserite i valori nella console Node.js. Come esempio standard, si trovano numerosi tutorial Node.js con la seguente linea di codice:

console.log('Hello world!');

La funzione Node.js indica di scrivere le parole “Hello World!” nella console e di confermarle col tasto invio.

Questo esempio mostra che Node.js funziona e può eseguire il codice JavaScript. In alternativa è possibile ottenere con la funzione console.log() anche il risultato di un calcolo nella console:

console.log (15 + 385 * 20);

Se desiderate avere informazioni dettagliate sulla versione Node.js installata e i suoi moduli, usate il seguente comando nella console Node.js.:

process.versions

Moduli di Node.js

Sulla base dell’ambiente di runtime V8, Node.js vi permette di realizzare server efficienti e altre applicazioni di rete scritti in JavaScript, linguaggio di scripting preferito sul web, e gli mette a disposizione una libreria molto ampia con moduli dei programmi. Questi moduli base integrati nell’ambiente di runtime comprendono funzionalità base come l’interazione con il sistema operativo, la comunicazione di rete o l’uso di meccanismi di criptazione. Inoltre Node.js, grazie al sistema di gestione dei pacchetti NPM integrato, offre la possibilità di installare moduli di programmi Node.js in maniera individuale.

Moduli di base

Per caricare uno dei moduli di base desiderati in un’applicazione Node.js, avete bisogno solo della funzione require(). Questa richiede una stringa, che specifica come parametro quale modulo debba essere caricato. Si tratta di uno dei moduli di base, corrispondente a modulname. Nei moduli installati successivamente, la stringa deve contenere il path del modulo, anche se il modulo si trova nella cartella corrente (in questo caso il path è semplicemente “/”). La seguente linea di codice mostra lo schema base, secondo il quale vengono caricati i moduli nelle applicazioni Node.js:

var module name = require('path/modulename');

Ad esempio, se si volesse caricare il modulo base “os”, il codice sarebbe il seguente:

var os = require('os');

Il modulo “os” offre diverse funzioni, con le quali è possibile inserire le informazioni sul sistema operativo. Se un utente desiderasse ad esempio conoscere la quantità di memoria di sistema disponibile in byte, dovrebbe usare il seguente codice:

var os = require('os');
var freemem = os.freemem();
console.log(freemem);

Nella prima linea il modulo “os” viene caricato nella variabile omonima “os”. Il segmento di codice os.freemem() nella seconda linea rappresenta una delle funzioni supportate dal modulo “os” e serve per individuare la quantità di spazio disponibile in byte nel sistema. Il valore individuato viene salvato in una delle variabili freemem. Nella terza riga infine viene usata la già citata funzione console.log(), per scrivere nella variabile freemem il valore salvato nella console.

Come mostra la finestra di Node.js, sono disponibili attualmente 5 GB di spazio di memoria.

Con altre funzioni del modulo “os”, come os.networkInterfaces() o os.hostname(), si può inserire una lista con porte di rete o determinare un host name. Altre informazioni sulle funzioni del modulo “os” e altri moduli base di Node.js, le trovate nella documentazione ufficiale del software.

Installare moduli manualmente

L’installazione manuale dei moduli di programmi avviene su Node.js attraverso il sistema di gestione dei pacchetti NPM. Le estensioni non devono quindi essere scaricate manualmente da siti web esterni e poi copiati nella rispettiva cartella. L’impegno necessario per l’installazione si limita ad una linea di codice sola, che viene inserita nella console del sistema operativo (non in quella di Node.js!). Nel sistema operativo Windows l’interprete del comando è cmd.exe, che è possibile lanciare inserendo lo stesso comando nella finestra di dialogo Windows (Windows-Taste + R). È necessario solo il comando npm install e il nome del modulo da installare:

npm install module name 

Tutti i moduli aggiuntivi per Node.js si possono visualizzare attraverso la funzione di ricerca sul sito web ufficiale di NPM.

Ad esempio, se si volesse installare il modulo “colors”, navigate nella console del sistema operativo fino alla cartella desiderata e confermate il seguente comando con il tasto di invio:

npm install colors

Questo screenshot mostra l’installazione del modulo “colors” nella cartella precedentemente creata C:\NodeModules:

Per caricare in un applicazione un modulo installato manualmente, si usa la funzione già menzionata require(). La seguente riga di codice mostra come si inserisce il modulo colors per evidenziare con colori i diversi inserimenti nella console Node.js:

var colors = require('C:/MyModules/node_modules/colors');
console.log('hello'.green); // makes text green 
console.log('I like cake and pies'.bold.red) // makes text red and bold 
console.log('inverse the color'.inverse); // inverts colors 
console.log('All work and no play makes Jack a dull boy!'.rainbow); // rainbow text

Nella prima linea di codice il modulo “colors” si carica nell’omonima variabile, con il path C:/NodeModules/node_modules/colors. Dalla riga 2 alla 5 ogni riga include una funzione console.log(), la cui stringa dovrebbe essere messa in evidenza in modo diverso attraverso il modulo color. È possibile adattare il colore (ad esempio .red, .green), mettere in evidenza il testo (.bold, .inverse) e inserire sequenze (z. B. .rainbow).

Esempio concreto: realizzare un proprio web server con solo 8 linee di codice

Uno degli usi standard di Node.js è lo sviluppo di un web server leggero. È possibile realizzare in pochi secondi un semplice server “Hello World!” grazie ai moduli pre-programmati “http”. Tutto quello di cui avete bisogno sono le seguenti otto linee di codice JavaScript, che potete creare in un text editor e salvare come file JavaScript in una cartella a vostra scelta. In questo tutorial Node.js il file si chiama webserver.js e viene posto nella cartella MyServer.

var http = require('http');
var port = 8080;

var server = http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello world!');
});
server.listen(port);

console.log('The server is available at http://127.0.0.1:' + port + '/');

Nella prima linea di codice il modulo del web server viene caricato attraverso la funzione require() e salvato nell’omonima variabile http.

var http = require('http');

Nella seconda linea poi si definisce il numero della porta 8080 e si salva nella variabile port.

var port = 8080;

Le linee da 3 a 6 definiscono, con l’aiuto della funzione createServer() del modulo http, una funzione callback, che riceve le richieste del browser in entrata (req) e offre una risposta (res). La risposta al browser è formata da un header, che contiene il codice http 200 (OK), oltre all’informazione riguardante il fatto che la parte restante della risposta è formata da un testo. Poi questo si inserisce attraverso la funzione res.end() nel contenuto della riposta “Hello World!”.

var server = http.createServer(function(req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hallo Welt!');
});

La settima riga del codice serve per collegare il web server al numero di porta definita nella riga 2 con la funzione listen().

server.listen(port);

L’ultima riga di codice è l’indirizzo web, sotto al quale si raggiunge il web server, della console.

console.log('The server is available at http://127.0.0.1:' + port + '/');

Nell’esempio si tratta di un indirizzo IP di localhost 127.0.0.1. Quindi il web server è a disposizione solo nell’ambito di un proprio sistema. Per avviarlo, eseguite il file JavaScript appena creato attraverso la console del sistema operativo. Per questo si usa un comando che segue il seguente schema:

node path/script.js

Nel nostro esempio, il comando diventa il seguente:

node C:\MyWebServer\webserver.js

Se avete inserito correttamente tutti i dati nello script e inserito il path del file senza errori, la console mostra l’indirizzo web del server:

Il web server è pronto a ricevere richieste. Potete verificarlo attraverso l’inserimento dell’indirizzo web 127.0.01:8080 in un browser qualsiasi. Se il server è stato programmato correttamente, vi apparirà una finestra del browser con la scritta “Hello World!”.

Per offrirti una migliore esperienza di navigazione online questo sito web usa dei cookie, propri e di terze parti. Continuando a navigare sul sito acconsenti all’utilizzo dei cookie. Scopri di più sull’uso dei cookie e sulla possibilità di modificarne le impostazioni o negare il consenso.