domenica 28 novembre 2010

Applicazioni Web con Python, Apache e MySql

Questo fine settimana piovoso mi ha relativamente costretto a stare a casa e non avendo un bip da fare... mi sn messo a smanettare un po con Python.Ne ho già parlato altre volte di python ma più lo guardo è più me piace.. E' fantastico!!!Semplice, veloce, orientato agli oggetti (in python tutto è un'oggetto...), ma soprattutto ha delle strutture dati che ti permettono di manipolare i loro contenuti con poche righe di codice (se non una) a differenza di altri linguaggi che ci vogliono decine di righe... Sto parlando di strutture come dizionari, tuple,liste e insiemi... Documentatevi un po...Comunque lo scopo di questo mio post è di illustrarvi come usare python per il web senza framework come Zope, Django, WebWare.. Ma soltando python, mysql e il tanto amato, robusto e sicuro server web Apache che ha contribuito in modo pesante alla crescita del World Wide Web..E vedremo come è facile implementare il pattern MVC (Model View Controller) con questa soluzione.
Ecco di quello che abbiamo bisogno sul pc:
  • Server Web Apache2
  • MySql server
  • il modulo apache per python mod_python
  • la libreria python MySQLdb
  • e ovviamente se nn installato nel vostro sistema dell'interprete Python
Installiamo il tutto con un bel:
sudo apt-get install apache2, libapache2-mod-python, mysql-server, python-mysqldb
nel comando non ho messo python di solito già installato se no aggiungetelo.. Ovviamente sto presumendo che abbiate Ubuntu o una derivata Debian. Se avete winzozz andate su Google per vedere come si installano..poi vi consiglio di utilizzare il plugin python per eclipse PyDev che ha completamento del codice e e tutte le altre future utili (.. anche se manca il supporto alle PSP,non sto parlando di playstation ve lo spiego avanti..., ma ho risolto costruendo un mio plugin ;) )Comunque ecco la struttura del progetto finale così vi fate un idea di dove posizionare i file sia se usate eclipse e sia se lavorate direttamente nelle directory con un editor di testo:

ovviamente questa è la struttura che ho scelto io ma nessuno obbliga a rispettarla...Come avete intuito dall'immagine metteremo sù un semplice guestbook...Nella cartella src ci stanno i sorgenti e tutto quello che andrà a finire nelle cartella del server Apache.Nella cartella resource ho messo un backup delle direttive da dare ad apache nella configurazione e l'sql per generare la tabella su db.Il file build.xml di Ant che vedete l'ho creato per non fare sempre un cp manuale verso il server e lanciare il tutto da eclipse quindi non serve per i nostri scopi (… comunque il progetto eclipse lo potete scaricare dal link che vi do in fondo al post).Nella cartella src abbiamo una struttura di package che contengono i sorgenti python legati al model (dto, dao ecc. si vede che ho una forte influenza Java...) poi un file index.py che fa da controller e nella sottocartella psp le pagine PSP (Python Server Page) che fanno da view....Le PSP di python non sono altro che un porting (molto limitato) di quello che sono le JSP per JAVA. Ossia mischiano codice Python a codice Html in modo da decidere la la logica di renderizzazione di quest'ultimo, ma capirete meglio continuando a leggere (mo lo sparata grossa e qualche guru java mi starà per bastonare, ma capitemi i miei sono tutorial introduttivi a chi è alle prime armi come lo ero io e non mi va di scendere nel tecnico).Per capire meglio cosa stiamo andando a costruire e come lavorano i vari componenti fra loro memorizzatevi il seguente schema:


Nello schema è rappresentato uno schema logico di come interagiscono i componeti fra di loro dopo una request http da parte di un client. Nella parte destra dello schema è rappresentata la directory dove metteremo il nostro progetto web una volta completo o comunque per testarlo con sottodirectory e files. Provo a spiegare il flusso:
  • Il client (il broweser web) richiede la risorsa index.py al server
  • il server Apache prende in considerazione la richiesta e dato che gli è stato detto che tutte le richieste del sito con estensione .py devono essere eleaborate dal mod_python passa la palla a quest'ultimo.
  • Il modulo python allora chiama la risorsa index.py
  • Index.py decide, in base alla funzione, che gli serve accesso al model per salvare o leggere uno o più oggeti Dedica.py allora chiama DedicaDao.py
  • DedicaDao.py tramite il modulo python MySQLdb (che mi sn dimenticato in figura) accede al db MySql e scrive o legge i dati restituendo il risultato a index.py
  • Una volta lavorati i dati, Index.py restituisce il tutto al mod_python dicendogli quale PSP elaborare e passandogli i dati necessari
  • Il mod_python elabora la PSP generando l'html da restituire al server Apache
  • Apache ricevuto l'html lo restituisce in risposta al client
  • Il client ricevuta la risposta con il codice html lo renderizza nella finastra del broweser.
Nello schema si vede nettamente il pattern MVC con il model rappresetato dal dedicadao.py e dagli oggetti dedica.py, le psp rapprestano lo strato view e il modulo index.py fa da controller.Ben ora smanettiamo un pochino.
CONFIGURAZIONE APACHE
Configuriamo apache dicendogli che per il progetto deve utilizzare il mod_python e quali risorse non rendere pubbliche.Quindi aprite con l'editor di testo il file: gedit /etc/apache2/sites-available/default e aggiungete le seguenti righe:


Come vedete sono tre direttive di directory: Prima direttiva:
  • PythonPath: serve per definire dove stanno i moduli python (i *.py del nostro model) da utilizzare come librerie da far caricare al mod_python.
  • AddHandler: abbiamo detto che per le richieste indirizzate a PyGuestbook con estensione .py sarà il modulo python a occuparsene
  • PythonHandler: diciamo al modulo python di utilizzare l'handler “publisher” (se leggete la documentazione ufficiale capirete meglio cosa fa e che ne esistono degli altri)
  • PythonDebug: lo mettiamo a ON per dire al modulo che siamo in fase di sviluppo e di presentare gli errore nel browser. In fase di produzione va messo a OFF in modo da ottenere il classico errore 500 (Internal error server) in caso di problemi.
Seconda direttiva:
  • diciamo ad apache che deve declinare qualsia richiesta verso la cartella contenente le librerie
Terza direttiva:
  • diciamo ad apache che deve declinare qualsia richiesta verso la cartella contenente le psp
Ovviamente ho dato per scontato che la cartella dove stanno i siti web apache sia /var/www/ e che siate su linux. Se siete su altri sistemi documentatevi sui vari path per il resto la configurazione è uguale. Qualsisi configurazione fate dovete restartare il server (e come proverete sulle vostre pelle anche quando fate modifiche al sito alcune volte va fatto), il comando:sudo /etc/init.d/apache2 restart
PREPARAZIONE MYSQL
Ora occupiamoci di MySql creando db e tabella. Accedete a mysql:
sudo mysql -u -p
inserite la password e avete a disposizione la command-line di mysql.
Create il db con il comando:
create database test_py;
spostatevi nel database creato:
use test_py;
create la tabella che ci serve:

SVILUPPO APPLICATIVO Cominciamo dal model ossia i dati da trattare. Come avete capito dalla creazione della tabella abbiamo un solo tipo di dato ossia Dedica quindi creamo l' oggetto python che rappresenta la nostra informazione ecco il codice:

Inserite il codice nel package piergiuseppe82.guestbook.model. Ogni instanza di tale oggetto sarà usata come oggetto di trasporto dati (DTO) tra DedicaDao.py e index.py. Fatto ciò andiamo a creare l'oggetto di servizio che si occuperà dell'accesso ai dati (DAO) sul db quindi nel package piergiuseppe82.guestbook.model.dao inseriamo DedicaDao.py:
Come vedete l'oggetto di servizio contiene un definizione di metodo per ogni operazione CRUD (Create Read Update Delete)che andiamo a fare sul db, più due metodi privati che servono per convertire i record estratti dal db in instanze di oggetti Dedica.

Da notare come viene usato il modulo python MySQLdb per ottenere la comunicazione con il server mySql e altri servizi per la comunicazione con quest'ultimo. (Non spiego ogni riga di codice perchè non è nello scopo del tutorial, se avete dubbi su cosa fa una determinata riga di codice andate sulla documentazione ufficiale di python.)

Ora entriamo nel vivo dell'applicazione legato al Web ossia decidiamo che l'applicazione deve rispettare i seguenti requisiti utente:

1.Inserimento di un nickname da salvare nella dedica e mantenere l'utente autenticato.
2.Inserimento e salvataggio su db di una nuova dedica dove se l'utente è gia presente prepolare il campo nickname con quello inserito altrimenti dare un messaggio di errore.
3.Visualuzzare l'elenco delle dediche, con possibilita di accesso alla modifica di ogni dedica e a una sua eventuale eliminazione.
4.Editare una dedica e salvarla su db.
5.Eliminare una dedica.

Ora trasformiamo qnd letto dai requisiti in servizi web in codice python. Ecco quindi di seguito il nostro index.py da mettere nella cartella src:



Come ho già detto il codice sopra fa da controller e sarà sempre lui a rispondere alle riquest del browser (ovviamente dopo che la request è passata da apache e resa leggibile da mod_python). Quindi capito come funzione e cosa fa avrete capito come usare il tutto e personalizzare come volete.

Ora devo spiegarvi come funziona la comunicazione tra il mod_python e il nostro modulo index.py.
Innanzi tutto dobbiamo parlare degli urls che identificano i nostri servizi.
Per richiamare un servizio del nostro modulo dobbiamo inserire nel browser:

http://localhost/PyGuestbook/index.py

dove PyGuestbook è la cartella alla quale puta apache /var/www/PyGuestbook contenente la nostra applicazione che fa pure da context nelle request. index.py è il modulo che vogliamo chiamare. In chiamate del genere ossia con alla fine solo il modulo il mod_python di default va a chiamare il metodo index definito nel modulo index.py. Se non viene definito restituisce un 404.

Ho detto di default perche abbiamo la possibilita di specificare quale servizio.
Ad esempio se vogliamo il servizio di nuova dedica basta richiamare dal browser :

http://localhost/PyGuestbook/index.py/nuova_dedica

Ad ogni chiamata verso un metodo del nostro controller il mod_python passa come parametro la request (nel codice se notate ogni metodo ha un parametro req che sta per Request).

Doppo aver fatto ciò il mod_python si aspetta in risposta dal metodo una stringa, il cui contenuto è il codice html da generare. Nel nostro caso però possiamo farcelo generare da mod_python stesso con le PSP (Python Server Pages), richiamando un suo servizio.

Commentiamo il codice.

All'inizio abbiamo gli import dei moduli python necessari come mod_python e i nostri moduli del model.

Poi abbiamo una serie di costanti private al modulo index.py che contengo stringhe usate nel codice.

Continuando ci sta la configuraione dell'oggeto usato per il logging.

Infine tutti i metodi richiamabili come servizi nell'url di richiesta del modulo index.py.
Ora vi spiego in modo generico cosa fanno i metodi e quale requisito vanno a soddisfare. Dopo vi spiegherò più dettagliatamente, per meglio farvi capire, come il codice fa a rispettare il requisito 2.

def index(req):
fa da punto di ingresso al modulo e da home page nel nostro caso. E va a soddisfare il requisito n°1 ossia il salvataggio in sessione dell'nickname usato come accesso al guestbook.

def nuova_dedica(req):
viene richiamato per accedere al servizio di inserimento di una nuova dedica e prepara il necessario per la corretta visulizzazione della psp di risposta. Va ad assolvere parte del requisito 2.

def salva_dedica(req,nickname,email,dedica):
viene richiamato in post dal form di inserimento di una nuova dedica il quale richiama il DAO per la persistenza di quest'ultima. Preparando poi il necessario per la renderizzazione della psp di risposta. Va ad assolvere parte del requisito 2.

def nuovo_nick(req):
questo servizio viene richiamato da un link sulla home page e si occupa di pulire la sessione del nick precedentemente inserito dalla index. Fa parte del requisito 1
def lista_dediche(req):
si occupa di chiamare il dao per avere tutte le dediche salvate e passarle come parametro alla psp che si occupa di visualizzarle. Va a rispettare il requisito 3.

def elimina_dedica(req,id):
si occupa di richiamare il dao per cancellare la dedica avente l'id passato nella request. Va ad assolvere pate del requisito 3 e soddisfa il requisito 5.

def edit_dedica(req,id):
va a prepare il necessario per la visualizzazione della dedica avente l'id passato in request richiamando il dao per ottenere tale instanza e per passarla poi alla psp che si occupa di renderziarla nel form di modifica di una dedica. Va a soddisfare parte dei requisiti 3 e 4.

def aggiorna_dedica(req,id,nickname,email,dedica):
questo servizio viene chiamato in post dal form di edit di una dedica e tramite il dao va ad aggiornare la dedica su db. Soddisfa parte del requisito 4.

Ora per meglio compredere come implementare il codice per far lavorare tutti i componenti insieme spiegherù in modo dettaglioto un solo caso d'uso. L'inserimento di una nuova dedica.

Il browser lato client richiede il servizio di inserimento di una nuova dedica tramite un link posto nella homepage e che punta all'url:
http://localhost/PyGuestbook/index.py/nuova_dedica

il mod_python risponde dato che vogliamo un *.py e nello specifico il modulo index.py poi si occorge che abbiamo specificato un servizio del modulo index.py ossia nuova_dedica quindi va a richiamare il metodo con lo stesso nome passando come parametro la request.

Risponde il metodo nuova dedica:


Cosa fa?
  • - prima logga l'ingresso al metodo
  • - richiama la sessione
  • - va a settare il tipo di risposta di defaul, nel nostro caso text/html
  • dopo si va a prepare il template ossia una psp da far elaborare al mod_python nel nostro caso la psp di visualizzazione del form per l'inserimento della nuova dedica.
  • - Quindi prepara due variabili messages conterrà eventuali messaggi e nickname conterrà il nickname.
  • - Prova a recuperare il nickname dell'utente messo in sessione dal metodo index e lo setta nella variabile.
  • - Se nickname è vuoto cambia la psp da elaborare e prepara il messagio di errore da far visulizzare alla psp dei messaggi.
  • - Quindi dice all'oggeto psp di elaborare il template passandogli un dizionario contenente le variabili necessarie.

Vediamo il sottocaso in cui il nickname è vuoto, abbiamo detto che va a elaborare la psp dei messaggi messages.psp ecco come è fatta:


per prima cosa abbiamo una direttiva psp di inclusione di un'altra psp nel nostro caso l'heder.psp (contenente la parte superiore dell'html generato; in fondo al post vi darò le psp). Quindi per uncludere una psp la direttiva è:

<%@ include file="nomedelfile.psp"%>

dopo la direttiva di inclusione abbiamo una parte di scriptlet o codice python. Esso va racchiuso nei tag
<% .. %>

Il codice in questione va a testare la variabile nickname passatagli dal metodo nuova_dedica del modulo index.py. Se questa non è vuota renderizza il contenuto della variabile preceduta dal testo Ciao e seguita dal tag di nuova riga, insomma il codice html da renderizzare se la condizione del costrutto if è rispettata. Quindi per renderizzare il contenuto di una variabile si usa la direttiva

<%=nomevaribile%>.

Dopo si apre e si chiude del codice python <% %> che può sembrare vuoto ma non lo è. In realta ci sta la chiusura dell'intentazione del costrutto if (se seguite questo tutorial vuol dire che avete un minimo di esperienza python e avete capito di cosa sto parlando). Infatti senza tale chiusura neanche il messaggio verrebbe visualizzato se nickname fosse vuoto.

Quindi la direttiva di visulizzazione del messaggio passato sempre come parametro.

In fine è presente un'altra direttiva di inclusione ma in questo caso del footer contenente la parte bassa del codice html.


Ora passiamo all'altro sotto caso ossia la varibile nickname viene recuperata dalla sesione dal metodo nuova_dedica. In tale caso abbiamo detto di elaborare la psp newDedica.psp, ecco come è fatta:


come vedete contiene oltra a header e footer il codice html per un form che permette i inserire Nickname, Email e messaggio di Dedica. Il nickname come vedete viene prevalorizzato con la direttiva python per la renderizzazione della variabile nickname passata come parameto al metodo run dell'oggetto psp del mod_python.

Da notare anche dove viene postato il form ossia

/PyGuestbook/index.py/salva_dedica

Andiamo aventi, diciamo che l'utente compili il form nel browser e prema il tasto salva dedica.

Il post contenuto nella request arriva al metodo salva_dedica del modulo index.py:


il metodo ottiene la request e notate in questo caso i paramtri del metodo che oltre alla request contiene anche i nomi dati ai campi del form. Infatti in tale paremetri il mod_python ha inserito i valori corretti senza la necessita da parte nostra di andarli a recuperare dall'oggeto req.

Fa le solite preparazioni con variabili da passare e psq da renderizzare. Quindi fa una validazione dei dati e se questi non rispettano la condizione di essere valorizzati cambia la psp da renderizzare con quella dei messaggi precedentemente vista e valorizza il valore della variabile messages.
Altrimenti va a richiamare il Dao per l'inserimento della nuova dedica.
Una volta inserita, sempre tramite il dao, va a recuperare la lista delle dediche per metterle nella variebile dediche da passare al template.

Bene diciamo che è andato tt bene quindi la psp da elaborare è listaDediche.psp:


quidi nella psp verifica prima se l'oggetto dedica lista_dediche non è vuoto in caso di esito positivo inizia a renderizzare la tabella con i nomi di colonna poi cicla la lista e per ogni dedica va a visulizzare data inserimento, nickname, email e messaggio. Nell'ultima colonna va a inserire i link per la modifica e l'eliminazione della dedica passando come parametro in request l'id della dedica in questione.

Bene ho finito e spero e che la spiegazione di questo caso d'uso posso aver chiarito il funzionamento del tutto ossia come usare python per il web nella sua forma + semplice rispettando comunque il pattern MVC.

Per testare non vi rimane che copiare il contenuto della cartella src in /var/www/PyGuestbook

Magari potete misurarvi nel risolvere alcuni bug che ci stanno in caso l'utente non rispetti il flusso di navigazione..

Di seguito i contenuti delle psp non trattate fino ad ora:

HEADER.PSP:



FOOTER.PSP:




INDEX.PSP



EDITDEDICA.PSP



e se clikkate qui il progetto eclipse.


Alla prossima.

7 commenti:

  1. Ciao, Innanzitutto i miei più sentiti complimenti. Era da tempo che cercavo una risorsa per poter iniziare a fare web app in python, apache e mysql (programmo da 10 anni in linguaggiacci :P tipo c# e compagnia bella ) e questo tuo post finalmente mi apre un pò la strada per poter far qualcosa anche sul mio os preferito (Ubuntu).
    Però sempre a causa della mia scarsa conoscenza tecnica di apache, mysql (conosco T-SQL alla perfezione ma questo è il mio primo test con MySql), ecc... ma seguendo tutte le tue istru, ho ricostruito la directory web con i sorgenti da te passati (adeguando chiaramente il nome dell'app) ma mi torna un erroraccio di tipo MOD_PYTHON ERROR se magari ci potessi dare un'occhiatina magari per capire dove ho sbagliato (o sto sbagliando) te ne sarei super grato : link su Pastebin http://pastebin.com/h2wYDMUb un salutone Aldo

    RispondiElimina
  2. Ciao mi fa piacere il tuo interesse,
    anche io in realtà a livello lavorativo uso prevalentamente tecnologie java e db oracle (quindi tt oracle). Però python mi affascina. Comunque il problema sta nel file index.py alla riga 25 dove ci sta la configurazione del file per il logging. File che non è necessario e che ho messo solo per comodità mia. Se non ti serve puoi eliminare dal codice la configurazione alla riga 25 e quindi tutti i punti dove ci stanno le loggate. Oppure crei il file al percorso /var/www/log/guestbook.log. Oppure togli la parte di filename=.... in modo che logga solo sullo standard di output.

    RispondiElimina
  3. Ciao, secondo il tuo consiglio, ho commentato tutto, ma ora il problema è un altro ... il maledetto 404 file not found...
    Allora per capire quanto mi devo considerare ignorante .... ho provato a fare uno stupidissimo .py con un semplice print 'ciao'... quando lo richiamo da browser ... esce il 404, ho pensato di sbagliare nella configurazione generica del web, ma creando un file prova.htm ed invece lo trova regolarmente... me sa che non ho capito niente. :(

    RispondiElimina
  4. Allora il file htm lo trova perchè non passa dall'handler del modulo_python. Quest'ultimo come da configurazione:

    PythonPath "['/var/www/PyGuestbook']+sys.path"
    AddHandler mod_python .py
    PythonHandler mod_python.publisher
    PythonDebug On

    Interviene solo per gli url che hanno come richiesta un ".py".

    Il 404 sta per risorsa non trovata come ben sai questo vuol dire che il modulo python non ha trovato la risorsa richiesta.. Le cause possono essere diverse o non trova il file index.py, ma a me sembra strano perchè se ti dava l'errore del log vuol dire che lo trova.. Oppure non lavora bene la psp. Usare l'istruzione print non ti serve a nulla perchè non lo vedrai mai sulla pagina. Prova con un return 'Ciao Mondo' al suo posto.

    Comunque ti consiglio arrivati a questo punto di farti un progetto da zero che stampa un hello world nel browser giusto per capire le dinamiche.

    Quindi in /var/www/ crei una nuova cartella helloworld

    rinnovagli i permessi non si sa mai:
    sudo chown -R :www-data /var/www/helloworld
    sudo chmod 755 /var/www/helloworld
    poi vai nel default di apache e aggiuggi la seguente configurazione:

    PythonPath "['/var/www/helloworld']+sys.path"
    AddHandler mod_python .py
    PythonHandler mod_python.publisher
    PythonDebug On


    poi nella cartella helloworld crei un file hello.py

    all'interno ci metti:
    def index(req):
    return "ciao mondo"

    per sicurezza fai restart di apache con
    sudo /etc/init.d/apache2 restart

    poi dal browser richiami:

    http://localhost/helloworld/hello.py

    e dovresti trovarti la scritta ciao mondo.

    Se va tutto ok prova a introdurre l'uso della psp per fare la stessa cosa.
    Se va male allora scrivi pure il problema che ti da che vedrò di risponderti e scriverò un post ad-hoc per un progetto tipo helloworld spiegando in modo dettagliato comandi e perchè del loro uso. In modo che chi ha problemi come te possa evitare i mal di testa ;)

    RispondiElimina
  5. Fantastico 6 stato preziosissimo!
    Allora, in parte per errori commessi con i path (non rispettavo il case sensitive nella denominazione delle directory ;P) e poi perché non avevo capito la logica della index.py, ovvero utilizzare def index(req) come metodo iniziale (tipo il page_load in c#) evvabbè a forza di tentativi, ho compreso...
    volevo solo chiederti un'ultima cosa, visto che sì funziona tutto ora, ma in parte grazie ad un macheggio... mi spiego
    non riuscendo ad importare i moduli posizionandoli su /var/www/primaapp/moduliGuestbook
    ho, grazie al detto se maometto... (:P) messo la directory moduliGuestbook in /usr/lib/python2.6,
    questo perché mentre sull'idle giochicchio con sys.path.append(), di fatto non so (non avendo mai realizzato un applicativo completo), come impostare di default la directory, in modo tale che venga vista dalla index.py (questo perché non uso tools che mi deployano l'app :) ).
    come potrei ovviare al problema (non so andar a modificare qualche file di conf... che chiaramente non conosco)?
    ti ringrazio ancora
    ciao ciao
    Aldo

    RispondiElimina
  6. Bene sono contento!!!
    Anche se io l'ho scritto il fatto del metodo index che viene chiamato di default. Anche per qnt riguarda le librerie è scritto, ve di la parte riguardo alla configurazione di apache... Comunque la ripeto. Prendi come riferimento il file di configurazione del post alla riga 2 ci sta un PythonPath ed è li che va detto al mod_python dove deve cercare le librerie python necessarie all'applicativo. Quindi nel tuo caso dovresti sostituire con
    PythonPath "['/var/www/primaapp/moduliGuestbook']+sys.path"
    e guarda l'immagine che visualizza la struttura dell'applicativo lato server per avere un'idea di come secondo me va organizzata, con una directory per le psp e poi la serie di directory che non sono altro dei package o per te che vieni da c# i namespaces....
    Comunque è a tentativi e test che si diventa padroni leggendo e basta si solo conosce... vai avanti così ;)

    RispondiElimina
  7. Confermo,
    in realtà, quasi sicuramente, il problema era dovuto alla denominazione della directory, visto che in realtà l'avevo messa la righa, ma non mi vedeva il package da web.. e io da ignorante (:P) ho pensato che caricare il package dall'idle significasse che funziona tutto.
    Vabbè, comunque, volevo nuovamente ringraziarti per l'aiuto, ma soprattutto per la disponibilità.
    un saluto
    Aldo

    RispondiElimina