Visualizzazione post con etichetta module. Mostra tutti i post
Visualizzazione post con etichetta module. Mostra tutti i post

lunedì, gennaio 12, 2015

Biblioteca: altro modulo per gestire la logica di business

Oggi implementeremo le funzionalità core di login e registrazione.

Non introdurremo ancora l'interazione con un server di dati per non appesantire l'esposizione, utilizzeremo una struttura dati in memoria per memorizzare le utenze, ma struttureremo il codice in maniera tale da non dover modificare la parte di gestione delle varie richieste presenti nel modulo delle rotte degli utenti: come? Ma utilizzando un altro bel raviolone :).

Prima di mettere il naso nel codice, navigate l'applicazione aggiornata su heroku per capire dove vogliamo andare a parare: https://whispering-peak-1284.herokuapp.com.

Provate a fare il login con username ivan e password saracino, che ha un profilo admin, notate come il menu presenti funzionalità (da implementare) di amministrazione.

Fate il logout per pulire la sessione e entrate nella pagina di registrazione, inserendo una vostra utenza; verrete rediretti alla pagina di login con un opportuno messaggio per effettuare l'autenticazione con le credenziali appena create: rifate il login e notate come cambia il menu e i messaggi di benvenuto presenti nell'header.

Ad ogni riavvio della applicazione, ovviamente tutte le utenze create non ci saranno più, rimarrà valido solo l'utente admin ivan saracino.

Per capire come abbiamo strutturato il codice, date un'occhiata al seguente diagramma di oggetti


Vi ricordo che moduli nel contesto javascript non sono altro che oggetti che espongono particolari proprietà che possono essere anche funzioni.

Il diagramma in alto non dice altro che il modulo app utilizza il modulo delle rotte utenti che a sua volta utilizza il modulo serviziUtenze: grazie a questa separazione di responsabilità tra moduli diversi sarà facile tenere sotto controllo la complessità del nostro progetto.

Il modulo app ha la responsabilità di configurare l'intera applicazione, tramite, ad esempio, l'utilizzo di moduli particolari di express per la gestione delle sessioni o il parsing del corpo delle richieste http: per poter effettuare il post dei form, dovremo includere nel codice del modulo app le seguenti istruzioni
 var bodyParser = require('body-parser');  
 ...  
 app.use(bodyParser.urlencoded({ extended: true }));   
In questo modo sarà possibile estrarre facilmente i parametri mandati in post al server al submit del form.

Il modulo utenti ha la responsabilità di definire le funzioni da eseguire per gestire le varie funzionalità legate agli utenti quali il login e la registrazione.
 var express = require('express');  
 var router = express.Router();  
 var serviziBiblioteca = require('../servizi/serviziUtenze');  
   
 router.route('/login')  
      .get(function(request,response) {  
           response.render('login');  
      })  
      .post(function(request,response){  
           var utente = serviziBiblioteca.login(  
                request.body.username,  
                request.body.password  
           );  
           if (utente) {  
                request.session.utente = utente;       
                response.render('home');  
           }  
           else {  
                response.render(  
                     'login',   
                     { loginFallito : 'Utente non abilitato'}  
                );  
           }  
             
      });  
   
 router.route('/registrazione')  
      .get(function(request,response) {  
           response.render('registrazione');  
      })  
      .post(function(request,response) {  
           var utente = {};  
           utente.username = request.body.username;  
           utente.password = request.body.password;  
           utente.email = request.body.email;  
           utente.profilo = 'semplice';  
           serviziBiblioteca.registra(utente);  
           response.render('login', {  
                registrazioneOk: 'Ora puoi effettuare il login!'});  
      });  
   
 router.route('/logout')  
      .get(function(request,response) {  
           request.session.destroy(function() {  
                response.redirect('/');  
           });  
      });  
   
 module.exports = router;  
Notate dal codice come sia possibile gestire una richiesta sia in get che in post.

Prendiamo ad esempio la richiesta /utente/login e osserviamo come nel caso di una richiesta get venga semplicemente renderizzata la pagina login.jade, mentre nel caso di una richiesta di tipo post venga utilizzato il modulo serviziUtenze per effettuare la vera e propria autenticazione.

Per estrarre i parametri dalla richiesta post, basta usare un codice del tipo request.body.nomeparametro.

Nel modulo serviziUtenze risiede la logica di business vera e propria, in questo caso abbiamo creato un modulo che espone due metodi login e registra che utilizzano un array di oggetti in memoria come base dati delle utenze inizializzato con l'utente admin ivan saracino.
 module.exports = (function(){  
      var utentiRegistrati = [  
           {  
                username : 'ivan',  
                password: 'saracino',  
                email: 'ivan.saracino@gmail.com',  
                profilo: 'admin'   
           }  
      ];  
      var login = function(username,password) {  
           var utentiLoggati = utentiRegistrati.filter(function(utente) {  
                return utente.username === username &&   
                    utente.password === password;  
           });  
           return utentiLoggati[0];  
      };  
      var registra = function(utente) {  
           utentiRegistrati.push(utente);  
           console.log(utentiRegistrati);  
      };  
      return {  
           login : login,  
           registra : registra  
      };  
 })();  
Per organizzare meglio i file, abbiamo previsto la presenza di una cartella servizi in cui memorizzare i file dei moduli della logica di business: non è difficile prevedere la presenza di un ulteriore modulo per la gestione dei libri quando andremo ad implementare tali funzionalità.

Per la generazione del menu dinamico, osserviamo il file menu.jade incluso nel layout
 if (!session.utente)  
      ul(class='nav nav-pils')  
           li(role='presentation')  
                a(href='/utente/login') Login  
           li(role='presentation')  
                a(href='/utente/registrazione') Registrati!  
 else  
      ul(class='nav nav-pils')  
           li(role='presentation')  
                a(href='/utente/logout') Logout  
           if (session.utente.profilo === 'semplice')  
                li(role='presentation')  
                     a(href='#') link semplice 1  
                li(role='presentation')  
                     a(href='#') link semplice 2  
           else  
                li(role='presentation')  
                     a(href='#') link admin 1  
                li(role="presentation")  
                     a(href='#') link admin 2  
Se esiste un utente in sessione, ne verrà controllato il profilo per generare opportunamente i link corretti.

Non abbiamo implementato una validazione dei parametri passati al server, nel prossimo post vedremo come affrontare questo argomento tramite l'utilizzo di un middleware opportuno.

Come al solito, ho aggiornato su github il progetto installato su heroku, in modo che possiate avere sempre tutto il codice a disposizione, in questo post è spiegato come utilizzare github.

Dopo aver fatto il pull del progetto, ricordatevi di eseguire npm install nella cartella di progetto per installare la dipendenza aggiuntiva per il modulo body-parser : se volete eseguire il programma in locale digitate node app.js e puntate il browser all'indirizzo http://localhost:5000.

Alla prox
Ivan

martedì, dicembre 02, 2014

Esercitazioni su node.js: esercizio 6

Ora i duri cominciano a giocare.

Testo dell'esercizio
Scrivere un programma per effettuare una richiesta HTTP di tipo GET ad un URL passato al programma come primo parametro dalla linea di comando.
Scrivere sulla console il contenuto testuale della rispota HTTP.

Suggerimenti
Per questo esercizio utilizzerete il modulo http, in particolare il metodo get(), presente nella distribuzione di base: la documentazione la potete trovare a questo link

La funzione di callback è di questo tipo function callback(response) ... dove response è un oggetto che rappresenta uno Stream in node.js: prossimamente approfondiremo il concetto di Stream, per ora ci basti sapere che uno Stream è un flusso di dati che puo' emettere particolari eventi gestibili da nostre funzioni di callback.
Gli eventi piu' importanti da gestire sono "data" , "error" e "end".
Ad esempio response.on("data", function(data) {}) ci permette di processare parzialmente i dati della risposta, non appena disponibili, in maniera asincrona: l'oggetto response puo' emettere più eventi "data" a seconda della dimensione della risposta effettiva del server.

L'oggetto response ha anche un metodo setEncoding(...) che ci permette di trattare il parametro data, ricevuto dalla funzione di callback degli eventi emessi dall'oggetto response, direttamente come una stringa nella codifica scelta (usate 'utf-8'). 

Buon Lavoro!

Una possibile soluzione
Più semplice del previsto, grazie ai moduli a disposizione della piattaforma node.js
 var http = require('http');  
   
 http.get(process.argv[2], function (response) {  
  response.setEncoding('utf8');  
  response.on('data', console.log);  
  response.on('error', console.log);
  response.on('end', function() {console.log("FINITO ... RICHIESTA CONCLUSA");});
 });  
Invochiamo il metodo get del modulo http passando come parametro la funzione di callback che gestisce la risposta.

La risposta è un oggetto che rappresenta uno stream di dati che emette eventi che hanno un nome: data,error, end.

L'evento data viene emesso ogni volta che c'è un blocco di dati disponibile per l'elaborazione, (ricordate che ora stiamo programmando in maniera asincrona).

L'evento error viene emesso ogni qual volta c'è un errore nella comunicazione con il server.
L'evento end viene emesso quando la richiesta è conclusa.

Notate che il secondo parametro del metodo on, nel caso di evento data ed error, deve essere una funzione che accetterà come parametri i dati effettivi ottenuti dal server: possiamo direttamente passare la funzione console.log che si occuperà di stampare a video i dati o i messaggi di errore.

Ho aggiornato su github il progetto, effettuate un git pull per aggiornare i sorgenti che avete in locale, come mostrato in questo post

Come al solito, per dubbi e/o domande: commentate!

Alla prox.
Ivan



Esercitazioni su node.js: esercizio 5

Testo dell'esercizio
Riprendete l'esercizio precedente e cercate di modularizzarlo con la tecnica vista nel post precedente.

Suggerimenti
Il lavoro principale deve essere fatta dal modulo esterno al programma.
Non dovete stampare la lista di file dal modulo, pensate ad una funzione di callback definita nel programma principale che verrà passata come argomento ad un metodo del modulo esterno.
Usate la forma ideomatica callback(err, data) per gestire nel programma principale eventuali errori generati nel modulo.

Buon Lavoro!

Una possibile soluzione
Il programma principale usa il modulo fileUtils, che esporta un oggetto che espone il metodo filtra per eseguire la lettura della directory e scartare i file che non hanno l'estensione voluta.
Il programma principale fornisce al metodo filtra la funzione di callback che verrà invocata nel modulo.
Si osservi come vengono gestiti gli errori: il modulo, che fa il lavoro sporco, passa l'eventuale errore come primo parametro della funzione di callback definita nel programma principale.
 var fileUtils = require('./fileUtils'),  
     dir = process.argv[2],  
     filtro = process.argv[3];  
     fileUtils.filtra(dir, filtro, function (err, list) {  
           if (err)  
             return console.error('Errore: ', err);  
           list.forEach(function (file) {  
             console.log(file);  
           });  
      });  

Il modulo fileUtils, usa una iife (se non vi ricordate cosa è andate rileggete questo post :)) per creare un oggetto che esponen il metodo filtra.
 var fs = require('fs');  
 var path = require('path');  
   
 module.exports = (function () {  
      var filtra = function(dir, filtro, callback) {  
           fs.readdir(dir, function (err, list) {  
              if (err)  
                 return callback(err);  
              list = list.filter(function (file) {  
                 return path.extname(file) === '.' + filtro;  
              });  
              callback(null, list);  
           });  
      };  
   
      return {  
           filtra : filtra  
      };  
 })();  
Per filtrare i file dalla lista completa, si puo' utilizzare il metodo filter presente in Array.prototype, 
questo metodo accetta come parametro una funzione che ritorna true se si vuole tenere nell'array il file corrente, false se lo si vuole togliere.

Ho aggiornato su github il progetto, effettuate un git pull per aggiornare i sorgenti che avete in locale, come mostrato in questo post

Come al solito, per dubbi e/o domande: commentate!

Alla prox
Ivan