Vai al contenuto

Pronti per la produzione

In questa lezione impari a

  • distinguere l'ambiente di sviluppo da quello di produzione
  • tenere i segreti fuori dal codice con le variabili d'ambiente
  • avviare l'applicazione con un server WSGI vero
  • pubblicare l'applicazione online
  • controllare un'applicazione con la lista di sicurezza prima di pubblicarla

La bacheca funziona sul vostro PC. Ma "funziona sul mio computer" non basta: un server pubblico è raggiungibile da chiunque, comprese persone che cercano di romperlo. Oggi la prepariamo per il mondo vero.

Il kit della lezione

Avviatelo come al solito con python app.py: si ferma con un errore. È voluto! Leggete il messaggio: manca il file .env. Lo sistemiamo nel primo paragrafo.

Sviluppo e produzione

Sviluppo (il vostro PC) Produzione (il server pubblico)
Chi lo usa voi tutti, anche malintenzionati
Debug acceso: errori dettagliati nel browser spento: mai mostrare il codice
Server app.run() (semplice, lento) un server WSGI (Waitress, Gunicorn)
Segreti possono essere di prova lunghi, casuali, fuori dal codice
Errori li vedete voi nel terminale pagina gentile per l'utente, dettagli nei log

Perché il debug acceso è pericoloso? Con debug=True, quando c'è un errore Flask mostra una pagina con il codice e una console Python interattiva nel browser: chi la raggiunge può eseguire comandi sul server. Su un server pubblico equivale a lasciare le chiavi nella porta.

1. I segreti fuori dal codice: il file .env

SECRET_KEY e API_KEY non devono stare in app.py: il codice finisce su GitHub, viene consegnato, copiato. Li mettiamo in un file a parte, .env, che non si condivide mai.

  1. Copiate .env.esempio e rinominate la copia in .env (con il punto davanti, senza altro).
  2. Generate una chiave casuale:
python -c "import secrets; print(secrets.token_hex(32))"
  1. Incollatela in .env, e fate lo stesso con una seconda chiave per l'API:
SECRET_KEY=3f9a1c…(64 caratteri)
API_KEY=8be20d…
FLASK_DEBUG=1

Ora python app.py parte. In app.py:

from dotenv import load_dotenv
load_dotenv()                     # legge .env e crea le "variabili d'ambiente"

app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY")
app.config["API_KEY"] = os.environ.get("API_KEY")

if not app.config["SECRET_KEY"] or not app.config["API_KEY"]:
    raise RuntimeError("Mancano SECRET_KEY o API_KEY: copia .env.esempio in .env e completalo")

Le variabili d'ambiente sono impostazioni che il sistema operativo passa al programma. Sui server veri si impostano dal pannello di controllo, senza bisogno del file .env; la libreria python-dotenv serve a usarle comodamente anche in sviluppo.

Il raise RuntimeError è una scelta precisa: meglio non partire che partire con una chiave vuota o di prova.

Se la SECRET_KEY cambia, tutti i cookie di sessione diventano non validi: gli utenti vengono scollegati. Se viene scoperta, chiunque può fabbricarsi un cookie e "diventare" qualsiasi utente.

2. .gitignore: cosa non va su GitHub

.env
.venv/
__pycache__/
*.db

Il file .gitignore dice a Git quali file ignorare. Il database resta fuori perché contiene i dati veri (e gli hash delle password); il .venv perché si ricrea con requirements.txt.

3. Le dipendenze con le versioni

flask==3.1.3
python-dotenv==1.2.4
waitress==3.0.2
requests==2.34.2

Con == e il numero di versione, chi installa il progetto (il server, un compagno, voi tra un anno) ottiene esattamente le stesse librerie. Per generare il file dalle librerie installate nel vostro ambiente virtuale: pip freeze > requirements.txt.

4. Un server vero: WSGI

app.run() avvia un server di sviluppo: gestisce male tante richieste insieme e non è pensato per la sicurezza. In produzione l'applicazione Flask viene "servita" da un server WSGI (Web Server Gateway Interface), lo standard con cui i server web parlano con le applicazioni Python.

  browser ──HTTPS──► server web (Nginx / PythonAnywhere) ──► server WSGI ──► app Flask
                     (certificati, file statici)            (Waitress,      (il vostro
                                                             Gunicorn)       codice)

Nel kit c'è avvia_server.py, con Waitress (funziona anche su Windows):

from waitress import serve
from app import app

serve(app, host="0.0.0.0", port=8080, threads=4)
python avvia_server.py        # poi http://127.0.0.1:8080

Su Linux si usa spesso Gunicorn: gunicorn -w 4 app:app (4 processi, l'oggetto app dentro app.py).

5. Gli errori in produzione: la pagina 500

@app.errorhandler(500)
def errore_server(errore):
    return render_template("500.html"), 500

Con il debug spento, se il codice solleva un'eccezione l'utente vede questa pagina gentile invece di un errore tecnico. Il dettaglio dell'errore finisce nel terminale (o nei log del server), dove lo legge solo chi gestisce il sito.

6. Cookie più sicuri

app.config["SESSION_COOKIE_HTTPONLY"] = True     # JavaScript non può leggere il cookie
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"    # non viene mandato dai form di altri siti

Il primo limita i danni di un eventuale attacco XSS; il secondo blocca la maggior parte degli attacchi CSRF. Su un server con HTTPS si aggiunge SESSION_COOKIE_SECURE = True (il cookie viaggia solo cifrato).

7. Dove pubblicare

Servizio Pregi Note
PythonAnywhere gratuito per progetti piccoli, pensato per Python, HTTPS incluso il più semplice per la scuola
Render, Railway pubblicazione automatica da GitHub piani gratuiti limitati
Un server Linux (il server del laboratorio, una VPS) controllo totale va configurato e mantenuto da voi
Un container Docker lo stesso pacchetto gira ovunque lo vediamo nei laboratori di quinta

Su PythonAnywhere, passo passo

  1. Create un account gratuito (Beginner) su pythonanywhere.com.
  2. Scheda Files: caricate lo zip del progetto (senza .env, .venv e bacheca.db), poi da una console Bash estraetelo con unzip nomefile.zip.
  3. Nella console Bash create l'ambiente virtuale e installate le librerie:
cd bacheca
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
  1. Create il file .env direttamente sul server (scheda Files → New file), con una SECRET_KEY nuova e FLASK_DEBUG=0.
  2. Scheda Web → Add a new web app → Manual configuration → Python 3.12.
  3. Nella stessa pagina: in Virtualenv indicate il percorso della cartella .venv; poi aprite il WSGI configuration file e sostituite tutto con:
import sys
sys.path.insert(0, "/home/VOSTRONOME/bacheca")
from app import app as application
  1. Premete Reload. La bacheca è online su https://VOSTRONOME.pythonanywhere.com. Se qualcosa non va, il link Error log nella scheda Web mostra l'errore.

Su PythonAnywhere non si usa né app.run() né Waitress: il server WSGI è il loro, e cerca nel file di configurazione una variabile chiamata application.

La lista di controllo prima di pubblicare

  • ☐ debug spento (FLASK_DEBUG=0)
  • ☐ SECRET_KEY lunga, casuale, letta dall'ambiente; mai la stessa dello sviluppo
  • ☐ nessuna password o chiave scritta nel codice
  • ☐ .env, .venv e database esclusi da Git
  • ☐ requirements.txt con le versioni
  • ☐ pagine 404, 403 e 500 personalizzate
  • ☐ tutte le route che modificano dati usano POST e sono protette (login + autorizzazione)
  • ☐ tutte le query usano i segnaposto ?
  • ☐ password salvate con hash
  • ☐ HTTPS attivo (PythonAnywhere e Render lo forniscono già)

Missioni

⭐ Missione 1 – Il file .env

Create il file .env come spiegato, avviate la bacheca con python app.py e poi con python avvia_server.py. Cosa cambia nel terminale? Poi mettete FLASK_DEBUG=0, provocate apposta un errore in una route (es. 1/0) e confrontate la pagina che vedete con debug acceso e spento.

⭐ Missione 2 – Caccia ai segreti

Cercate in tutto il progetto (in VS Code: Ctrl + Maiusc + F) le parole password, key, secret. Per ogni risultato, spiegate se è un problema o no e perché.

⭐⭐ Missione 3 – Online

Pubblicate la bacheca su PythonAnywhere seguendo i passi della lezione. Consegnate il link e una breve relazione: i passaggi seguiti, gli errori incontrati (con il messaggio dell'Error log) e come li avete risolti.

⭐⭐ Missione 4 – Il registro degli errori

Fate in modo che ogni errore 500 venga scritto, con data e ora, in un file errori.log, mentre l'utente continua a vedere la pagina gentile.

Aiutino

Nella funzione errore_server potete usare app.logger.error(...). Per scrivere su file, all'avvio: import logging e logging.basicConfig(filename="errori.log", level=logging.ERROR, format="%(asctime)s %(message)s").

⭐⭐⭐ Missione 5 – La revisione

Scambiatevi il progetto con un altro gruppo e controllate la loro applicazione con la lista di controllo della lezione. Scrivete un breve rapporto: per ogni punto, ok oppure il problema trovato (con il file e la riga) e come risolverlo.