Nelle scorse lezioni abbiamo introdotto le principali caratteristiche di Node.js, vedendo alcuni primi esempi di semplici server HTTP realizzati con poche righe di codice. Nella terza lezione abbiamo visto quali sono i moduli più importanti, tra quelli nativi, e qual è la procedura per l’installazione di un nuovo modulo.
Siamo finalmente pronti a mettere assieme le nozioni viste finora per creare un server che possa gestire delle richieste rivolte su URL diversi dalla classica richiesta della index.html
(o simile).
Il nostro obiettivo è quello di realizzare un piccolo server che sia in grado di risolvere le diverse casistiche del protocollo HTTP. Oltre alla situazione “normale”, descritta dal codice HTTP 200, vogliamo gestire anche altri casi, come ad esempio lo scenario “page not found” (HTTP 404) e “internal server error” (HTTP 500). A tal fine ci verrà incontro il modulo url, al quale abbiamo già accennato nelle lezioni precedenti. Prima di passare alla scrittura del server vero e proprio, facciamo un po’ di pratica con questo modulo. Consideriamo il seguente script
1 2 3 | var url = require('url') ; var parsed = url.parse('http://www.hostingtalk.it') ; for (e in parsed) console.log('parsed url property -> ' + e) ; |
La prima riga serve a “caricare” il modulo, come già visto nelle lezioni precedenti. Subito dopo abbiamo il primo esempio di utilizzo delle API offerte dal modo: la funzione parse
permette di eseguire il parsing dell’URL passato come argomento. Il risultato è un oggetto simile a quelli disponibili in JavaScript che, pur assomigliando ad un array, va trattato in modo diverso. Per questo motivo nella terza riga scorriamo con un ciclo tutte le proprietà dell’oggetto, e le stampiamo usando la console di Node.js. Il risultato dovrebbe essere qualcosa del genere
1 2 3 4 5 6 7 | parsed url property -> protocol parsed url property -> slashes parsed url property -> host parsed url property -> hostname parsed url property -> href parsed url property -> pathname parsed url property -> path |
in questo modo scopriamo, tramite l’introspezione, quali sono i nomi delle proprietà esposte dall’oggetto. Se vogliamo visualizzare solo una di queste scriviamo
1 | console.log('parsed.hostname -> ' + parsed.hostname) ; |
Che in questo caso visualizzerà il valore della proprietà “hostname” ricavata dall’URL iniziale.
Il modulo URL
Il modulo URL
L’esempio di pagina precedente mostra come possiamo spezzettare, o meglio “tokenizzare”, le diverse proprietà di un generico URL. Metodi di questo tipo ci serviranno per riconoscere qual è l’URL richiesto dall’utente e gestirlo conseguentemente. Il codice visto finora è stato scritto proprio per capire come possiamo ricavare i valori che ci interessano dall’URL in ingresso, come ad esempio protocollo, nome dell’host, percorso ecc. Se invece vogliamo soltanto stampare a video, nella console di esecuzione, tutti i valori “parsati” dall’URL (ad esempio in fase di debug), possiamo usare un’istruzione più comoda, ovvero
1 | console.log(parsed) ; |
Che produrrà come risultato
1 2 3 4 5 6 7 | { protocol: 'http:', slashes: true, host: 'www.hostingtalk.it', hostname: 'www.hostingtalk.it', href: 'http://www.hostingtalk.it/', pathname: '/', path: '/' } |
In questo caso facciamo attenzione a non inserire altre stringhe o etichette prima dell’oggetto, altrimenti, invece della lista qui sopra, verrà stampato soltanto il riferimento all’identificatore dell’oggetto. In altre parole non dobbiamo scrivere cose del genere
1 | console.log('Risultato: ' + parsed) ; |
Osservando le proprietà visualizzate quando stampiamo la variabile parsed
, dovremo riconoscere la struttura caratteristica degli Object Literal usati in JavaScript. Questo ovviamente non è un caso, perché Node.js è stato strutturato appositamente per ricalcare da vicino il paradigma di programmazione JavaScript.
Ovviamente, oltre ai metodi per effettuare il parsing degli indirizzi, abbiamo anche i metodi complementari, che si occupano di formattare una o più stringhe per produrre un URL valido. Ad esempio
1 2 3 | console.log('Format -> ' + url.format( { host: 'www.hostingtalk.it', protocol: 'http' } )) ; |
Stamperà a video l’indirizzo dell’URL scritto nella maniera usuale, ovvero
1 | Format -> <a title="http://www.hostingtalk.it" href="../../../">http://www.hostingtalk.it</a> |
Notiamo, tra l’altro, che anche in ingresso del metodo format
abbiamo usato la sintassi degli Object Literal JavaScript: se non siamo pratici di questa sintassi è bene tornare a rispolverare le nozioni JavaScript relative all’utilizzo di questi oggetti, perché risulteranno utili in molte circostanze (e non solo con Node.js).
Un server http “completo”
Un server http “completo”
Mettendo assieme gli snippet presentati nelle pagine precedenti con quelli relativi alla gestione del file system della terza lezione, abbiamo tutti gli strumenti necessari per realizzare un piccolo server HTTP “completo” dal punto di vista delle funzionalità elementari. La gestione degli URL servirà a capire quale indirizzo è stato richiesto, in modo da poter restituire il risultato HTTP 404 senza perder tempo a cercare risorse nel file system (per le directories non servite). Al contrario, per quelli indirizzi che sappiamo corrispondere a directory effettivamente disponibili nel file system, possiamo usare gli snippet della terza lezione per restituire il contenuto di una risorsa.
Il codice del nostro “serverino” sarà il seguente
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // Loadin modules and creating the HTTP server var url = require("url") ; var http = require("http") ; var server = http.createServer(onRequest).listen(8080) ; console.log("Server is up and running") ; // 1st callback: on success function onRequest(req, res) { var path = url.parse(req.url).pathname ; console.log("Server parsed path: " + path) ; switch (path) { case '/': res.writeHead(200, {'Content-Type': 'text/html'}) ; res.end('Hello World!n') ; break; default: pageNotFound(res) ; } } // 2nd callback: on failure function pageNotFound(res) { res.writeHead(404) ; res.end('404 - Not Found') ; } |
per motivi di brevità abbiamo omesso le sezioni di codice relative all’accesso al file system, perché già discusse nella lezione precedente. Queste sezioni potrebbero essere inserite all’interno del codice dello switch
, ad esempio
1 2 3 | switch (path) { case '/home': // Servire alcune risorse nella directory home |
La procedura di merge tra il codice appena visto, e quello relativo alla gestione del file system, dovrebbe essere abbastanza semplice. Invitiamo il lettore a provarci in prima persona, come “esercizio per casa”: il modo migliore di imparare è sempre quello di fare un po’ di pratica. Come suggerimento ricordiamo che il nostro server dovrà gestire anche il fallimento di accesso ad una risorsa, che in generale si tradurrà nella risposta HTTP 500.