Quando parliamo di server Web possiamo riferirci a tre cose diverse, e il primo esercizio di questo capitolo è imparare a distinguerle: possiamo riferirci all'hardware, al software, oppure ancora a entrambi, considerando il loro lavoro comune. L'ambiguità non è pigrizia terminologica: nella pratica quotidiana la macchina e il programma che ci gira sopra sono così saldati che li si nomina con la stessa parola, ma all'esame è bene tenerli separati, perché rispondono a domande diverse.
Dal punto di vista hardware, un server web è un computer che ospita il software server web e i file che compongono un sito Web: documenti HTML, immagini, fogli di stile CSS, script JavaScript e così via. È collegato alla rete Internet e supporta l'interscambio di dati con gli altri device collegati al Web. Sono queste due proprietà — la presenza fisica dei file e la connessione permanente — a fare di una macchina qualsiasi un server.
Dal punto di vista software, un server web include diverse parti e componenti che controllano come l'utente accede ai file del sito web e che si occupano della comunicazione HTTP. La componente che se ne occupa si chiama server HTTP, ed è definita da due capacità precise: è in grado di comprendere gli URL e di risolvere i path corrispondenti, ed è in grado di comprendere il protocollo di comunicazione HTTP. Notate che questa definizione contiene già in miniatura tutto il capitolo: la prima capacità diventerà la sezione sulla path translation, la seconda la sezione sul protocollo.
Hardware e software non sono due definizioni alternative fra cui scegliere: sono due livelli della stessa cosa. La richiesta HTTP arriva al web server inteso come macchina, viene accettata dal server HTTP inteso come programma, e viene servita dal server Web inteso come insieme di componenti che cercano la risorsa. Ogni volta che leggete "server web" in una frase, chiedetevi a quale dei tre livelli si riferisce.
La descrizione operativa di un server Web sta in tre verbi: deve ospitare, processare ed effettuare il delivery delle pagine Web ai browser. Le risorse di cui parliamo sono documenti HTML, che a loro volta possono includere immagini, fogli di stile e script. Il server HTTP è la componente incaricata di ricevere le richieste HTTP da parte del client e di mandare al client le risposte HTTP, inviandogli le risorse richieste in risposta alle richieste ricevute.
Il ciclo completo, quello che conviene sapere raccontare a memoria, è una catena di cinque passi. Ogni volta che un browser richiede una risorsa a un Web server, invia una richiesta HTTP. La richiesta arriva al web server (hardware). Il server HTTP (software) del web server accetta la richiesta. Il server Web (software) si occupa di cercare la risorsa. Se trova la risorsa richiesta, allora invia una risposta HTTP al client, mandando la risorsa richiesta; altrimenti viene inviato e visualizzato dal browser un messaggio di errore 404 Not found.
Vale la pena fermarsi su un punto che le slide enunciano in modo asciutto: il server HTTP deve rispondere a ogni richiesta HTTP, inviando la risorsa richiesta oppure inviando un messaggio di errore. Non esiste il silenzio come risposta legittima. Questa è una proprietà preziosa per chi progetta il client, perché significa che il codice che gira nel browser può sempre contare su un esito, e deve sempre gestirne almeno due.
La distinzione fra server Web statico e dinamico è additiva, non alternativa. Un server Web statico è dotato di HTTP server e ospita le risorse del sito Web, che invia al client senza alcuna elaborazione. Un server Web dinamico è un server web statico a cui si aggiungono altre parti e componenti che consentono la computazione lato server; queste componenti aggiuntive solitamente sono application server oppure database. Si chiama "dinamico" perché l'application server aggiorna ed elabora i file prima di inviarli al client tramite il server HTTP: per esempio le pagine HTML potrebbero essere riempite di contenuti presi dal database tramite query, oppure potrebbe essere eseguito codice PHP.
Considerando le componenti e le funzionalità di base, gli step principali del lavoro di un server Web sono quattro, e sono l'indice delle prossime sezioni: hosting dei file, comunicazione con protocollo HTTP (il server HTTP), traduzione dei path, gestione dei contenuti statici e dinamici.
Il server web deve ospitare i file del sito web: documenti HTML, immagini, fogli di stile CSS, script JavaScript, font, video e così via. Tecnicamente è possibile ospitarli su un personal computer, ma ovviamente non è né efficace né efficiente; è necessario quindi utilizzare un server web dedicato con alcune caratteristiche precise.
| Caratteristica | Perché serve |
|---|---|
| Sia sempre acceso e funzionante | Una risorsa raggiungibile solo a tratti non è una risorsa del Web: l'URL deve valere in ogni momento |
| Sia sempre collegato a Internet | Il server è per definizione il lato che attende la connessione, non quello che la inizia |
| Abbia sempre lo stesso IP address | Senza un indirizzo stabile la risoluzione del nome verso la macchina non è affidabile |
| Manutenzione da parte di un provider terzo | In alcuni casi è utile: un buon hosting provider può rappresentare un elemento chiave |
Le tre caratteristiche tecniche descrivono esattamente ciò che oggi comprate quando affittate una macchina in cloud o un servizio di hosting: uptime, connettività e indirizzo stabile. La quarta voce, il provider di terze parti, non è un requisito tecnico ma organizzativo, e le slide lo segnalano come "elemento chiave" proprio perché è la variabile che di solito determina se le prime tre vengono davvero rispettate.
Un server Web deve contenere un server HTTP: questo significa che è in grado di comunicare con i client condividendo il protocollo HTTP (HyperText Transfer Protocol). Nelle esercitazioni la definizione viene ripresa in forma più formale: HTTP è un protocollo a livello applicativo usato come principale sistema per la trasmissione delle informazioni sul web, utilizzando un'architettura client-server. Le due proprietà che il corso vi chiede di ricordare, e soprattutto di saper usare per spiegare altri fenomeni, sono che HTTP è testuale e che è stateless.
HTTP è testuale: tutti i comandi e i messaggi sono plain-text e leggibili da un utente umano. Non è una curiosità estetica; è la ragione per cui potete diagnosticare un problema di rete leggendo con gli occhi ciò che passa sul filo, ed è la ragione per cui gli esempi che seguono si possono stampare su una slide senza bisogno di alcun decodificatore.
HTTP è stateless: server e client non hanno memoria delle eventuali precedenti comunicazioni occorse tra di loro. Le conseguenze sono molto concrete. Basandosi sul solo HTTP, un server non è in grado di ricordarsi della password immessa dall'utente, né del punto di una transazione in cui l'utente si trova. Per fare ciò è necessario un application server che sia in grado di occuparsi di questi task, oppure altri meccanismi specifici, per esempio lato client.
La domanda "perché HTTP è stateless e cosa comporta" è quasi sempre seguita da "e allora come fa un sito a ricordarsi che ho fatto il login?". La risposta che le slide autorizzano è netta: non lo fa HTTP. Lo stato viene ricostruito sopra il protocollo, o da un application server che gestisce sessione e transazione, o da meccanismi lato client. Tenete presente il legame con la sezione 8: è proprio questo vuoto lasciato da HTTP che giustifica l'esistenza degli application server.
Un secondo malinteso da smontare subito riguarda il rapporto fra pagina e richiesta. Una pagina Web non corrisponde a una richiesta HTTP: corrisponde a una collezione di richieste, e quelle richieste possono andare a server diversi. Il documento Web che contiene testo, un'immagine, un video e un banner pubblicitario genera una richiesta per ogni risorsa, e il browser le indirizza a chi ospita quella specifica risorsa: il Web server per il documento, il foglio di stile e l'immagine di intestazione, un Video server per il video, un Ad server per il banner.
GET index.html # al Web server
GET styles.css # al Web server
GET header.png # al Web server
GET video.mp4 # al Video server
GET advert.jpg # all'Ad server
L'esempio che segue è preso dalle esercitazioni: una richiesta grezza verso google.it effettuata con un client HTTP a riga di comando, e la risposta che arriva indietro. Cliccate sulle righe per leggerne il significato; è l'esempio più utile del capitolo, perché mostra tutte le proprietà discusse finora in forma leggibile.
La risposta a GET / HTTP/1.1 con Host: google.it non è la home page: è un 301 Moved Permanently con header Location: http://www.google.it/. Non è un errore ed è facile scambiarlo per tale. Il server sta dicendo al client che la risorsa vive stabilmente a un altro URL, e il body HTML che accompagna la risposta serve solo ai client che non seguono i redirect da soli. Un browser esegue una seconda richiesta verso l'URL indicato in Location senza mostrarvi nulla: dal vostro punto di vista di utenti è una pagina sola, dal punto di vista del protocollo sono due scambi.
Per poter trovare la risorsa richiesta dal client HTTP è fondamentale mappare il percorso di un URL in una delle due cose seguenti: una risorsa di un file system locale, nel caso di richieste di risorse statiche, oppure il nome di un programma interno o esterno, nel caso di richieste di risorse dinamiche. Per una richiesta statica il path URL specificato dal client è relativo alla root directory del server web.
L'esempio canonico è http://www.example.com/path/file.html. Il client traduce questo URL in una connessione a www.example.com con una richiesta GET della risorsa /path/file.html. Il server web ospitato da www.example.com appenderà il path ricevuto con la richiesta alla sua root directory: su un server Apache solitamente è /home/www, e il risultato sarà /home/www/path/file.html; su una macchina Unix potrebbe essere /var/www/htdocs, e il risultato sarà /var/www/htdocs/path/file.html.
URL richiesto http://www.example.com/path/file.html
path della richiesta GET /path/file.html
document root (Apache) /home/www => /home/www/path/file.html
document root (Unix) /var/www/htdocs => /var/www/htdocs/path/file.html
A quel punto il server web legge il file, se esiste, e manda questa risorsa al browser. La risposta descrive il contenuto del file e contiene il file stesso; in alternativa viene inviato un messaggio di errore che comunica che il file non esiste oppure che non è raggiungibile. Notate la sfumatura: "non esiste" e "non raggiungibile" sono condizioni diverse, e nel mondo reale corrispondono a codici di stato diversi, ma dal punto di vista del ciclo che stiamo descrivendo appartengono entrambe al ramo dell'errore.
La path translation è il punto in cui il Web smette di essere un'astrazione e diventa un file su un disco. Sapete rispondere a queste tre domande? Rispetto a cosa è relativo il path di una richiesta statica (alla root directory del server web, non alla radice del file system). Cosa cambia se la risorsa è dinamica (il path viene mappato sul nome di un programma interno o esterno, non su un file da leggere). Chi decide la document root (la configurazione del server: /home/www su Apache, /var/www/htdocs su una macchina Unix). Sono tre righe di risposta che valgono un'intera domanda.
Un server Web potrebbe dover gestire sia contenuto statico sia contenuto dinamico, e la differenza sta tutta in quanto lavoro c'è fra il disco e la risposta. Nel caso di contenuto statico, il server Web si occupa semplicemente di recuperare le risorse e di inviarle al client così come sono. Nel caso di contenuto dinamico, il server Web — più precisamente l'application server — processa il contenuto della risorsa oppure genera la risorsa on-the-fly, ottenendo dati da un database. Questa seconda soluzione è più flessibile, ma sicuramente più complessa.
Il ciclo visto nella sezione 2 si arricchisce quindi di una diramazione interna. Per ogni richiesta ricevuta, il server HTTP controlla che l'URL richiesto corrisponda effettivamente a un file. Se il server HTTP trova il file, allora o il server web invia la risorsa richiesta al client, oppure un application server costruisce ed elabora il file, creando e generando la risorsa. Se il processo di creazione o elaborazione non è possibile, oppure se la risorsa non esiste, allora il server web ritorna al browser un messaggio di errore.
Gli application server disponibili sono molti. Alcuni offrono supporto e funzionalità a specifiche categorie di siti web, come blog, wiki o e-shop; altri, i Content Management System, offrono funzionalità e supporti più generici. La scelta fra le due famiglie è una decisione architetturale vera: il supporto specializzato vi dà molto gratis nel dominio previsto e poco fuori da esso, quello generico è il contrario.
| Contenuto statico | Contenuto dinamico | |
|---|---|---|
| Cosa fa il server | Recupera la risorsa e la invia così com'è | Processa la risorsa o la genera on-the-fly con dati dal database |
| Chi la produce | Il server HTTP leggendo il file system | L'application server |
| Path translation verso | Una risorsa del file system locale | Il nome di un programma interno o esterno |
| Bilancio | Semplice e veloce | Più flessibile, ma più complesso |
Finora abbiamo seguito una richiesta. Un server reale però ne riceve molte insieme, e il modo in cui le gestisce è una scelta architetturale con due grandi famiglie: l'approccio concorrente e l'approccio Single-Process-Event-Driven.
L'approccio concorrente permette al server web di gestire richieste multiple da parte dei client allo stesso tempo, e si realizza con uno di tre metodi. Nel multi-processing un processo padre inizia diversi processi figli e distribuisce le richieste in arrivo a questi processi figli: ogni processo figlio è responsabile della gestione di una singola richiesta, mentre il processo padre è responsabile del monitoraggio del carico e decide se i processi debbano essere killed oppure forked. Nel multi-threaded vengono creati più processi single-threaded. Il metodo ibrido è una combinazione dei due precedenti: sono creati processi multipli e ogni processo inizia thread multipli, con ogni thread che gestisce una connessione; usando più thread in un singolo processo si ottiene meno carico sulle risorse del sistema.
| Metodo | Come funziona | Chi fa cosa |
|---|---|---|
| Multi-processing | Un processo padre avvia processi figli e distribuisce loro le richieste in arrivo | Ogni figlio gestisce una singola richiesta; il padre monitora il carico e decide kill o fork |
| Multi-threaded | Vengono creati più processi single-threaded | La concorrenza nasce dalla molteplicità dei processi |
| Ibrido | Processi multipli, ciascuno con thread multipli | Ogni thread gestisce una connessione; meno carico sulle risorse di sistema |
L'alternativa è l'approccio Single-Process-Event-Driven: un solo processo che non moltiplica processi o thread per servire più client, ma organizza il lavoro attorno agli eventi. È il secondo dei due approcci elencati dalle slide, ed è il ponte diretto verso il capitolo successivo.
Le slide di questa lezione nominano il modello Single-Process-Event-Driven senza svilupparlo, perché è esattamente il modello di esecuzione che verrà smontato in dettaglio parlando di Node.js e del suo event loop. Tenete quindi in sospeso la domanda che l'approccio solleva — se il processo è uno solo, come fa a non bloccarsi mentre aspetta il disco o il database? — e recuperatela quando incontrerete la programmazione asincrona: le due cose sono la stessa storia raccontata a due livelli diversi.
Attorno al server Web vivono altri due tipi di server che compaiono in quasi ogni architettura reale. Gli application server offrono un ambiente per l'esecuzione delle applicazioni e spesso agiscono come interfaccia ai database server, gestendo il flusso dei dati così da inviarli all'utente. I database server gestiscono l'archiviazione e il recupero dei dati, fornendo i dati richiesti dagli altri server sulla base delle informazioni che questi richiedono.
Sull'application server le slide aggiungono tre precisazioni che vale la pena ricordare alla lettera. Primo: gli application server si occupano di fornire la computazione relativa alla business logic delle applicazioni, così da generare il contenuto dinamico. Secondo: i client di un application server sono a loro volta applicazioni, e potrebbero essere dei server Web oppure altri application server — quindi l'utente umano non è quasi mai il client diretto di un application server. Terzo: la comunicazione tra un application server e i suoi client potrebbe avvenire via messaggi HTTP, ma non è mandatorio; altri protocolli di comunicazione potrebbero essere utilizzati, sulla base del tipo di application server.
Nella pratica i confini sfumano. In molti casi application server e server Web sono implementati insieme, e soprattutto non esistono standard o recommendation che definiscano le proprietà dei server Web e degli application server. Le slide portano un esempio istruttivo di questa fluidità terminologica: un reverse proxy potrebbe fornire anche le funzionalità di un load balancer, ma a loro volta la maggior parte dei load balancer sono anche reverse proxy.
L'assenza di standard che definiscano cosa sia un "server Web" e cosa un "application server" ha una conseguenza pratica su cui si inciampa spesso: i nomi dei prodotti non sono definizioni. Due strumenti che si presentano con la stessa etichetta possono coprire insiemi di funzioni diversi, e uno stesso strumento può cambiare ruolo a seconda di come lo configurate. Quando descrivete un'architettura — anche quella del vostro elaborato — descrivete le funzioni svolte da ciascun componente, non solo i loro nomi.
Oltre a ospitare, tradurre path e rispondere, i server Web offrono un corredo di funzionalità che nessuna delle sezioni precedenti richiede, ma che tutte le installazioni reali usano. Le trovate qui riunite, perché all'esame vengono chieste spesso come elenco.
| Funzionalità | A cosa serve |
|---|---|
| Logging | I server web hanno di solito la capacità di gestire log file con le informazioni relative alle richieste dei client e alle risposte del server: permette di raccogliere dati statistici e di fare analisi dei log |
| Authentication | Accesso tramite nome utente e password, proteggendo l'accesso ad alcuni tipi di risorse ospitate sul server |
| Supporto a HTTPS | Usando SSL o TLS: permette la connessione sicura (criptata) al server sulla porta 443 invece che sulla porta 80 |
| Compressione dei contenuti | Permette di ridurre la dimensione delle risposte, per esempio usando gzip, limitando la larghezza di banda dedicata alla comunicazione |
| Virtual hosting | Permette di gestire più siti Web usando un solo IP address |
| Supporto a file di grandi dimensioni | Per esempio per gestire file con dimensione maggiore di 2 GB su sistemi operativi a 32 bit |
| Bandwidth throttling | Limita la velocità delle risposte, così da non saturare la rete e da poter gestire le richieste di più client |
Due voci di questa tabella hanno un riscontro diretto nell'esempio di traffico grezzo che avete letto nella sezione 4. L'header Accept-Encoding: gzip, deflate della richiesta è il modo in cui il client dichiara di saper leggere risposte compresse: la compressione dei contenuti non è una decisione unilaterale del server, è una negoziazione. L'header Host: google.it è ciò che rende possibile il virtual hosting: dato che l'indirizzo IP può essere condiviso da molti siti, è il valore di Host a dire al server quale dei siti ospitati state chiedendo.
Tre di queste funzionalità — compressione, bandwidth throttling e la gestione separata dei file di grandi dimensioni che incontrerete nella prossima sezione — sono tutte facce dello stesso problema: la banda è una risorsa condivisa e finita. Il server non può renderla più grande, quindi la amministra: comprime ciò che si comprime, rallenta ciò che può essere rallentato, e separa i flussi che hanno esigenze diverse.
I server Web hanno limiti di carico definiti: possono gestire un numero limitato di connessioni client concorrenti per IP address (e porta IP) e possono servire solo un certo numero massimo di richieste per secondo. Quel numero non è una costante universale ma dipende da cinque fattori: i setting del server, il tipo di richieste HTTP, il tipo del contenuto (statico o dinamico), la presenza e l'utilizzo di una cache per il contenuto, e i limiti di hardware, software e sistema operativo su cui il server sta girando. Se il server web si avvicina o raggiunge i suoi limiti di carico, allora diventa sovraccarico e quindi non risponde in modo opportuno.
Le cause elencate dalle slide sono sei, e conviene leggerle notando che solo una parte di esse è ostile.
L'ultima voce merita attenzione perché descrive il meccanismo per cui i guasti si propagano: togliete una macchina da un gruppo, e il traffico che serviva si riversa sulle altre, che possono a loro volta cedere. Le slide riportano anche un esempio concreto di sovraccarico osservato, datato lunedì 23 marzo 2020, ore 19:55.
Un server sovraccarico si riconosce da quattro sintomi. Le richieste sono gestite con ritardi, che possono variare da 1 secondo fino a qualche centinaio di secondi. Vengono restituiti al client errori 500, 502, 503 e 504; in alcuni casi potrebbero essere restituiti anche errori non correlati, come 404 o 408. Le connessioni TCP sono rifiutate o interrotte prima che il contenuto sia inviato ai client. In alcuni rari casi, solo parte del contenuto potrebbe essere inviata al client, e questo potrebbe dipendere da risorse del sistema non disponibili.
| Codice | Nome | Cosa segnala in situazione di overload |
|---|---|---|
500 | Internal Server Error | Il server non è riuscito a completare l'elaborazione della richiesta |
502 | Bad Gateway | Un server che fa da tramite ha ricevuto una risposta non valida da monte |
503 | Service Unavailable | Il servizio non è disponibile: è il codice tipico del sovraccarico |
504 | Gateway Timeout | Il tramite ha atteso invano la risposta del server a monte |
404 / 408 | Not found / Request Timeout | Errori non correlati che pure possono comparire durante un sovraccarico |
Il fatto che sotto carico possano comparire anche errori non correlati, come il 404, è la ragione per cui la diagnosi a partire dal solo codice di stato è ingannevole. Un 404 isolato dice "risorsa non trovata"; una raffica di 404 insieme a 503 e a connessioni TCP interrotte dice tutt'altro, e cioè che il server sta cedendo. Leggete i codici in massa e nel tempo, non uno alla volta: è esattamente per questo che il logging è nell'elenco delle funzionalità standard.
Le contromisure elencate dalle slide si dispongono su tre piani: la rete, la distribuzione dei contenuti e le risorse della macchina.
Sul piano della rete si tratta di gestire il traffico usando firewall per bloccare traffico non desiderato, manager di traffico HTTP, e manager di larghezza di banda con shaping del traffico per limitare i picchi nell'uso della rete.
Sul piano della distribuzione dei contenuti si usano domain name diversi per gestire differenti contenuti attraverso diversi server web (per esempio http://images.example.com e http://www.example.com); si usano domain name diversi o diversi computer per separare i file di grandi dimensioni da quelli di piccole e medie dimensioni, con l'idea di usare una cache per i file piccoli e medi e di servire in modo più efficiente i file grandi (dai 10 ai 1000 MB) con setting differenti; e si applicano tecniche di web caching.
Sul piano delle risorse si può usare molti server web software per computer, ognuno legato al proprio IP address; usare molti server web hardware raggruppati insieme, così che possano agire o essere visti come un solo server web; aggiungere più risorse hardware al server (RAM, memoria su disco); impostare i parametri del sistema operativo; utilizzare in modo più efficiente i programmi per server Web; e usare workaround, in particolare se il server web si occupa di generare contenuto dinamico.
Su questa sezione la domanda tipica non è l'elenco a memoria ma il collegamento causa-sintomo-rimedio. Provate a percorrerlo: il contenuto dinamico abbassa il numero massimo di richieste al secondo (fattore di carico) → sotto picco compaiono 503 e connessioni rifiutate (sintomo) → si risponde con caching, separazione dei contenuti su domini e server diversi, e workaround specifici per la generazione dinamica (rimedio). Saper fare questo percorso in entrambe le direzioni vale molto più che ripetere le sei cause in ordine.
Il termine Model-View-Controller viene coniato nel 1978 da Trygve Reenskaug e Adele Goldberg. La loro idea era basata sul fatto che MVC e le sue varianti potessero costituire un Pattern Language, ovvero un linguaggio condiviso per la discussione, la definizione e la formalizzazione dei problemi e delle loro soluzioni. Il concetto di Pattern Language ha influenzato anche i concetti alla base del libro Design Patterns.
Vale la pena soffermarsi su questa origine, perché spiega perché MVC sia sopravvissuto a quarant'anni di cambiamenti tecnologici pur non essendo una tecnologia. MVC nasce come vocabolario: un modo per dire "questa parte fa questo" in modo che due progettisti diversi si capiscano. È per la stessa ragione che oggi troviamo le sue varianti — MVVM, MVP, Flux — descritte come dialetti della stessa lingua, e non come invenzioni indipendenti.
Il Model gestisce il comportamento e i dati relativi al dominio; risponde alla view su interrogazioni sul proprio stato; risponde a richieste di cambiamento dello stato provenienti dal controller. In una formulazione più compatta, il model è l'informazione che viene manipolata dall'applicazione, o anche solo dal widget.
La View visualizza il modello in maniera utile per l'interazione utente; è la visualizzazione del modello sullo schermo. Un punto spesso trascurato: possono esistere view multiple per lo stesso modello. È questa possibilità a giustificare la separazione: se la corrispondenza fosse sempre uno a uno, dividere costerebbe senza rendere.
Il Controller definisce il comportamento; mappa le azioni degli utenti agli aggiornamenti del model; seleziona le view per l'interazione con l'utente. Detto in altri termini, il controller riceve gli input dall'utente e decide cosa significhino e cosa si debba fare.
| Ruolo | Responsabilità | Con chi parla |
|---|---|---|
| Model | Comportamento e dati del dominio; persistence e business logic | Risponde alle interrogazioni della View sul proprio stato e alle richieste di cambiamento dello stato dal Controller |
| View | Visualizza il modello in maniera utile per l'interazione utente | Interroga il Model; possono esistere view multiple per lo stesso modello |
| Controller | Definisce il comportamento, interpreta gli input | Mappa le azioni degli utenti sugli aggiornamenti del Model e seleziona le View |
La conseguenza progettuale è esplicita: vanno implementate separatamente le classi e gli elementi che implementano il modello funzionale dell'oggetto da quelli che implementano la view. Il controller, invece, può essere associato in molti casi alla view. Questa è la variante di MVC in cui i ruoli di View e Controller vengono concentrati in un unico oggetto: da un lato la presentation, dall'altro l'input, entrambi rivolti verso l'utente, con il Model che resta separato a custodire persistence e business logic.
All'inizio degli anni 2000 diversi framework web hanno iniziato ad adottare MVC: Spring, Ruby on Rails, PHP, ASP.net e altri. In questi framework avviene però uno spostamento che è il cuore di questa sezione: il Controller si fa carico della gestione della richiesta HTTP iniziale.
Il modello risultante si legge in quattro affermazioni. Il Controller funge da entry-point dell'applicazione, al posto della View, e si occupa della comunicazione HTTP. La View si occupa di assemblare bundle HTML, JS e CSS nella risposta che deve essere inviata al browser, e quindi gestisce il template. Il Model gestisce la business logic e lo storage. HTML e JS contengono alcuni elementi della logica di interazione dell'applicazione, come per esempio l'handler del click di un bottone che invia un'azione al Controller tramite una XMLHttpRequest. E infine, il punto che smonta l'equivoco più comune: il browser è esterno a questo approccio, non è coinvolto.
Perché insistere sul fatto che il browser è esterno? Perché in questa forma di MVC tutti e tre i ruoli vivono sul server. Il browser non ospita né Model né View né Controller: riceve un bundle già composto e lo visualizza. Quando poi l'utente clicca un bottone, l'handler in JavaScript non "aggiorna la view locale": invia un'azione al Controller tramite una XMLHttpRequest, cioè torna a bussare al server. È esattamente questo assetto che le Single Page Application metteranno in discussione.
Se vi viene chiesto "chi è l'entry-point in MVC per il Web e cosa cambia rispetto a MVC classico", la risposta in una frase è: nel Web l'entry-point è il Controller — non la View — perché è lui a gestire la richiesta HTTP iniziale, e la View si riduce ad assemblare il bundle HTML/JS/CSS della risposta. Aggiungete la frase che vale il punto in più: il browser è esterno all'approccio, non è uno dei tre ruoli.
Con l'avvento dei browser più moderni, alcune innovazioni — XMLHttpRequest, DOM API stabili, ECMAScript 6 — hanno portato a maggiore flessibilità e migliori performance. Da qui la definizione e la diffusione di web app, incluse le Single Page Application, con una maggiore computazione ed esecuzione lato client. I web framework moderni supportano questa maggiore complessità lato client, mantenendo lo sviluppo delle applicazioni produttivo e predicibile.
Concretamente, questi framework introducono la possibilità di creare dei bundle di HTML, JS e CSS statici. Queste Single Page Application includono la logica, scritta in JS, per effettuare delle richieste HTTP API per un insieme di risorse servite da Controller API, che solitamente rispondono con del JSON.
Confrontate questa tavola con la precedente e vedrete lo spostamento in atto. Nella tavola 3.3 il browser riceveva pagine; qui riceve un'applicazione, e da quel momento in poi ciò che attraversa la rete non è più presentazione ma dati. Il Controller lato server non scompare, ma si sdoppia: uno SPA Controller che serve /index.html e con esso il bundle, e degli API Controller che rispondono alle richieste GET e POST con JSON.
Questo ci ha portato alla situazione attuale, in cui React, Vue e Angular sono i Web framework più utilizzati. Tutti questi framework hanno un qualche tipo di View. Vue e Angular applicano un pattern Model-View-ViewModel (MVVM). React si basa su Flux, che supporta il concetto del data flow unidirezionale e può essere considerato un'alternativa al pattern MVC: le action sono inviate a uno store attraverso un dispatcher, e le modifiche allo store sono propagate fino alla view.
Osservate come la sezione 4 e questa sezione si chiudano l'una sull'altra. Avevamo detto che una pagina genera molte richieste verso server diversi; qui vediamo che in una SPA quelle richieste cambiano natura: la prima porta il codice dell'applicazione, tutte le altre portano dati. È lo stesso protocollo stateless di prima, usato per un compito diverso — e resta stateless, motivo per cui la gestione dello stato migra dentro l'applicazione, cioè dentro lo Store o il ViewModel di cui parlano le due sezioni seguenti.
MVC nel Web si applicava sviluppando un controller che permette alla View di manipolare il data model, e indirettamente questo può portare a far comunicare la View con il backend. Con le evoluzioni del Web è emerso però come l'idea del controller nel pattern MVC non sia sempre efficace ed efficiente, per esempio nelle Single Page Application. Da questo nasce l'approccio del ViewModel, che va a sostituire il Controller e che si sposta sul frontend dell'applicazione.
La definizione da tenere a mente è quasi una negazione: il ViewModel non è un controller. Si comporta come un binder per i dati di View e Model. Ne segue una conseguenza che spesso viene enunciata al contrario nei riassunti frettolosi: il pattern MVVM non separa Model e View, ma con l'approccio del data-binding supporta la comunicazione diretta tra View e Model.
Lo svantaggio è dichiarato senza giri di parole: il ViewModel consuma una quantità di memoria considerevole se paragonato all'approccio con il controller. Non è consigliato quindi per semplici interfacce utente, perché considerato eccessivo. Per questo motivo il pattern MVVM è per lo più utilizzato dalle SPA — o single function application — sul Web: l'esecuzione di applicazioni più "consistenti" diventerebbe troppo pesante.
| MVC nel Web | MVVM | |
|---|---|---|
| Elemento intermedio | Controller | ViewModel, che sostituisce il Controller |
| Dove vive | Sul server, come entry-point della richiesta HTTP | Sul frontend dell'applicazione |
| Come lavora | Permette alla View di manipolare il data model | Fa da binder tra i dati di View e Model, con data-binding |
| Separazione View/Model | Mantenuta e mediata dal Controller | Non separa: supporta la comunicazione diretta |
| Costo | Più leggero | Memoria considerevole, eccessivo per interfacce semplici |
| Dove si usa | Framework server-side degli anni 2000 (Spring, Ruby on Rails…) | SPA; Vue e Angular applicano MVVM |
Il passaggio da Controller a ViewModel è la stessa migrazione descritta nel capitolo 1, vista da dentro il codice: la logica attraversa il confine client/server e si stabilisce nel browser. Il Controller era l'oggetto che rispondeva a una richiesta; il ViewModel è l'oggetto che tiene sincronizzati due stati. Cambia l'unità di lavoro — dal singolo giro HTTP alla continuità di una sessione applicativa — e con essa cambia il costo: si paga in memoria ciò che si guadagna in reattività.
Le ultime due varianti completano la famiglia. Nel pattern Model-View-Presenter il Presenter riceve l'input dagli utenti attraverso la View, processa i dati dell'utente con il supporto del Model e passa il risultato alla View. Il Presenter manipola il Model e aggiorna la View. View e Presenter sono disaccoppiati, separati tra loro, e comunicano attraverso un'interfaccia: è questo il tratto distintivo di MVP rispetto a MVC, dove il controller può in molti casi essere associato alla view.
Flux è basato su data flow unidirezionale. La motivazione è un problema di scala: man mano che un'applicazione cresce in termini di complessità, diventa sempre più difficile gestire i cambi di stato con gli update della view, in particolare quando questi cambi derivano da fonti differenti. React affronta il problema creando una nuova View come funzione di uno stato non modificabile: l'applicazione può cambiare creando una nuova istanza di Model nello state tree; quando React vuole aggiornare l'applicazione, sostituisce una parte del suo state tree con un nuovo oggetto, che "triggera" la creazione di una o più nuove View.
Da qui la differenza più citata rispetto a MVC. Mentre il Model in MVC rappresenta i dati che persistono e che saranno renderizzati dalla View, Flux divide le responsabilità del Model in due: le Actions/API sono usate per la business logic, lo Store è usato per la gestione dello stato. E, come già visto nella sezione 13, le action sono inviate a uno store attraverso un dispatcher, e le modifiche allo store sono propagate fino alla view.
Coniato nel 1978 da Reenskaug e Goldberg come Pattern Language. Il Controller riceve gli input dall'utente e decide cosa significhino e cosa si debba fare; mappa le azioni sugli aggiornamenti del Model e seleziona le View. Il Model gestisce comportamento e dati del dominio, la View lo visualizza in modo utile all'interazione. Nel Web il Controller diventa l'entry-point della richiesta HTTP.
Il ViewModel sostituisce il Controller e si sposta sul frontend. Non è un controller: è un binder per i dati di View e Model. Non separa View e Model, ma con il data-binding ne supporta la comunicazione diretta. Costa memoria considerevole, quindi è eccessivo per interfacce semplici e si usa soprattutto nelle SPA. Lo applicano Vue e Angular.
Il Presenter riceve l'input dagli utenti attraverso la View, processa i dati con il supporto del Model e passa il risultato alla View; manipola il Model e aggiorna la View. Il tratto distintivo: View e Presenter sono disaccoppiati e comunicano attraverso un'interfaccia.
Data flow unidirezionale, alternativa a MVC, alla base di React. Le action sono inviate a uno store attraverso un dispatcher, e le modifiche allo store si propagano fino alla view. Divide le responsabilità del Model: Actions/API per la business logic, Store per la gestione dello stato. La View è una funzione di uno stato non modificabile.
Il modo più economico per tenere insieme le quattro varianti è ricordare chi sta in mezzo tra View e Model e che tipo di rapporto impone. In MVC c'è il Controller, che interpreta gli input e sceglie le view. In MVP c'è il Presenter, disaccoppiato dalla View e raggiungibile solo attraverso un'interfaccia. In MVVM c'è il ViewModel, che non media ma lega: data-binding e comunicazione diretta. In Flux non c'è un mediatore ma un verso: il flusso è unidirezionale e il Model si spacca in Actions/API e Store. Sapendo questo, sapete anche perché Vue e Angular stanno da una parte e React dall'altra.
Ci si può riferire all'hardware, al software o a entrambi. Come hardware un server web è un computer che ospita il software server web e i file che compongono un sito Web (documenti HTML, immagini, CSS, script JavaScript), è collegato a Internet e supporta l'interscambio di dati con altri device. Come software include diverse componenti che controllano come l'utente accede ai file e che si occupano della comunicazione HTTP: il server HTTP, in grado di comprendere gli URL e di risolvere i path corrispondenti, oltre che di comprendere il protocollo. Il ciclo è questo: il browser invia una richiesta HTTP, che arriva al web server (hardware); il server HTTP (software) accetta la richiesta; il server Web (software) cerca la risorsa; se la trova invia una risposta HTTP con la risorsa richiesta, altrimenti invia un 404 Not found che il browser visualizza. Il server deve rispondere sempre: o con la risorsa, o con un errore.
Il server Web statico è dotato di HTTP server, ospita le risorse del sito e le invia al client senza alcuna elaborazione. Il server Web dinamico è un server statico a cui si aggiungono componenti che consentono la computazione lato server, tipicamente application server oppure database. Si chiama dinamico perché l'application server aggiorna ed elabora i file prima di inviarli al client tramite il server HTTP: per esempio le pagine HTML potrebbero essere riempite con contenuti presi dal database tramite query, oppure potrebbe essere eseguito codice PHP.
Perché server e client non hanno memoria delle eventuali precedenti comunicazioni occorse tra di loro. La conseguenza pratica è che, basandosi sul solo HTTP, un server non è in grado di ricordarsi della password immessa dall'utente né del punto di una transazione in cui l'utente si trova. Per ottenere questo comportamento serve un application server che si occupi di questi task, oppure altri meccanismi specifici, per esempio lato client. HTTP è inoltre testuale: comandi e messaggi sono plain-text e leggibili da un essere umano.
http://www.example.com/path/file.html.Il client traduce l'URL in una connessione a www.example.com con una richiesta GET della risorsa /path/file.html. Il server web appende il path ricevuto alla propria root directory: su Apache tipicamente /home/www, con risultato /home/www/path/file.html; su una macchina Unix potrebbe essere /var/www/htdocs, con risultato /var/www/htdocs/path/file.html. Il server legge il file, se esiste, e lo manda al browser in una risposta che descrive il contenuto del file e contiene il file stesso; altrimenti invia un messaggio di errore che comunica che il file non esiste o non è raggiungibile. Per le risorse dinamiche il path non viene mappato su un file ma sul nome di un programma interno o esterno.
Due: l'approccio concorrente e l'approccio Single-Process-Event-Driven. Il primo permette di gestire richieste multiple allo stesso tempo con tre metodi. Multi-processing: un processo padre avvia processi figli e distribuisce loro le richieste in arrivo; ogni figlio gestisce una singola richiesta, il padre monitora il carico e decide se i processi vadano killed o forked. Multi-threaded: vengono creati più processi single-threaded. Ibrido: processi multipli, ognuno con thread multipli, ogni thread gestisce una connessione; usando più thread in un singolo processo si ottiene meno carico sulle risorse di sistema.
L'application server offre un ambiente per l'esecuzione delle applicazioni, fornisce la computazione relativa alla business logic per generare il contenuto dinamico e spesso agisce da interfaccia verso i database server, che gestiscono archiviazione e recupero dei dati. I client di un application server sono a loro volta applicazioni — server Web o altri application server — e la comunicazione può avvenire via HTTP ma non è mandatorio. In molti casi application server e server Web sono implementati insieme, e soprattutto non esistono standard o recommendation che ne definiscano le proprietà: per esempio un reverse proxy può fornire anche funzionalità di load balancer, e la maggior parte dei load balancer sono a loro volta reverse proxy.
Un server gestisce un numero limitato di connessioni concorrenti per IP address e porta, e un numero massimo di richieste al secondo che dipende da: setting del server, tipo di richieste HTTP, tipo di contenuto (statico o dinamico), presenza e uso di una cache, e limiti di hardware, software e sistema operativo. Fra le funzionalità aggiuntive che entrano in gioco su questo terreno ci sono il logging (per dati statistici e analisi), l'authentication, il supporto a HTTPS con SSL o TLS sulla porta 443 invece della 80, la compressione dei contenuti con gzip, il virtual hosting per più siti su un solo IP address, il supporto a file di grandi dimensioni (oltre 2 GB su sistemi a 32 bit) e il bandwidth throttling. I sintomi dell'overload sono: ritardi da 1 secondo a qualche centinaio di secondi; errori 500 (Internal Server Error), 502 (Bad Gateway), 503 (Service Unavailable) e 504 (Gateway Timeout), con la possibilità di errori non correlati come 404 o 408; connessioni TCP rifiutate o interrotte prima dell'invio del contenuto; in rari casi l'invio di solo parte del contenuto.
Il termine Model-View-Controller viene coniato nel 1978 da Trygve Reenskaug e Adele Goldberg, con l'idea che MVC e le sue varianti potessero costituire un Pattern Language, cioè un linguaggio condiviso per discutere, definire e formalizzare problemi e soluzioni; il concetto ha influenzato anche le basi del libro Design Patterns. Il Model gestisce comportamento e dati del dominio, risponde alle interrogazioni della view sul proprio stato e alle richieste di cambiamento provenienti dal controller. La View visualizza il modello in maniera utile per l'interazione utente, e possono esistere view multiple per lo stesso modello. Il Controller definisce il comportamento, mappa le azioni degli utenti sugli aggiornamenti del model e seleziona le view. Esiste una variante in cui i ruoli di View e Controller sono concentrati in un unico oggetto.
All'inizio degli anni 2000 framework come Spring, Ruby on Rails, PHP e ASP.net adottano MVC, e il Controller si fa carico della richiesta HTTP iniziale: diventa l'entry-point dell'applicazione al posto della View e gestisce la comunicazione HTTP. La View assembla i bundle HTML, JS e CSS nella risposta da inviare al browser, gestendo il template. Il Model gestisce business logic e storage. HTML e JS contengono alcuni elementi della logica di interazione, come l'handler del click di un bottone che invia un'azione al Controller tramite XMLHttpRequest. Il browser è esterno a questo approccio: non è coinvolto come componente del pattern.
Innovazioni come XMLHttpRequest, DOM API stabili ed ECMAScript 6 hanno portato flessibilità e migliori performance, favorendo web app e SPA con maggiore computazione lato client. I framework moderni permettono di creare bundle di HTML, JS e CSS statici: la SPA viene servita da uno SPA Controller (tipicamente su /index.html) e include la logica JS per effettuare richieste HTTP API verso Controller API, che solitamente rispondono con JSON appoggiandosi a un API layer (GraphQL, REST) e a un DB. Oggi i framework più usati sono React, Vue e Angular: tutti hanno un qualche tipo di View, Vue e Angular applicano MVVM, React si basa su Flux.
Perché con le evoluzioni del Web è emerso che l'idea del controller non è sempre efficace ed efficiente, per esempio nelle SPA: in MVC per il Web il controller permette alla View di manipolare il data model, e indirettamente questo può portare la View a comunicare con il backend. Nasce così il ViewModel, che sostituisce il Controller e si sposta sul frontend. Il ViewModel non è un controller: si comporta come un binder per i dati di View e Model, quindi MVVM non separa Model e View ma con il data-binding ne supporta la comunicazione diretta. Lo svantaggio è il consumo di memoria considerevole rispetto all'approccio con controller: è sconsigliato per interfacce utente semplici, dove risulta eccessivo, ed è usato per lo più da SPA o single function application.
In MVP il Presenter riceve l'input dagli utenti attraverso la View, processa i dati con il supporto del Model e passa il risultato alla View; manipola il Model e aggiorna la View. La differenza rispetto a MVC è che View e Presenter sono disaccoppiati e comunicano attraverso un'interfaccia. Flux è basato su data flow unidirezionale ed è considerato un'alternativa a MVC: nasce perché, al crescere della complessità, gestire i cambi di stato con gli update della view diventa difficile, soprattutto quando i cambi arrivano da fonti differenti. React crea una nuova View come funzione di uno stato non modificabile e aggiorna l'applicazione sostituendo una parte del proprio state tree. Mentre il Model di MVC rappresenta i dati che persistono e che saranno renderizzati dalla View, Flux divide le responsabilità del Model: Actions/API per la business logic e Store per la gestione dello stato, con le action inviate allo store attraverso un dispatcher.