Salta al contenuto principale

Come creare una pagina web con Python

Profile picture for user luca77king

Avete mai pensato di costruire una pagina web da zero, senza affidarsi a framework o librerie esterne? Python, noto per la sua semplicità e versatilità, permette di farlo in modo relativamente semplice. Molti credono che realizzare un sito web richieda obbligatoriamente strumenti complessi, ma in realtà, grazie alle librerie built-in come http.server, possiamo gestire richieste HTTP, rispondere con HTML e persino generare contenuti dinamici direttamente dal nostro server. Questo approccio non solo consente di capire meglio come funziona il web "dietro le quinte", ma rappresenta anche un esercizio didattico fondamentale per chi vuole imparare networking, protocolli web e gestione di server senza complicazioni.

Creare un sito non significa solo scrivere HTML, CSS e JavaScript: significa capire come il browser comunica con il server, come le richieste vengono interpretate, come il server elabora i dati e come invia la risposta corretta. Python offre la possibilità di partire da un server minimo e, passo dopo passo, aggiungere funzionalità più avanzate, come template engine rudimentali, gestione di form, parametri URL e contenuti dinamici. In questo modo, è possibile costruire applicazioni web interattive e comprendere i concetti chiave che poi ritroveremo anche nei framework più complessi come Flask, Django o FastAPI.

In questa guida partiremo da un server base, per arrivare a sistemi più avanzati che gestiscono template, parametri e form HTML. Ogni esempio è breve, ma esaustivo, così che possiate testare immediatamente il codice senza installare nulla.

Introduzione al Protocollo HTTP

Per costruire una pagina web è fondamentale capire il protocollo HTTP. HTTP (HyperText Transfer Protocol) è il linguaggio con cui client e server comunicano. Il browser invia richieste al server, che risponde con dati: pagine HTML, immagini, file CSS o JavaScript. Questo protocollo rappresenta il cuore pulsante del World Wide Web e la sua comprensione è essenziale per chiunque voglia sviluppare applicazioni web, indipendentemente dal linguaggio o dal framework utilizzato.

Quando digitiamo un indirizzo nella barra del browser e premiamo invio, stiamo in realtà inviando una richiesta HTTP al server che ospita quel sito. Il server riceve questa richiesta, la interpreta, elabora eventuali dati necessari e risponde con il contenuto appropriato. Questo scambio avviene in frazioni di secondo, ma dietro questa semplicità apparente si nasconde un meccanismo ben strutturato di comunicazione client-server che possiamo replicare e controllare completamente con Python.

Le richieste HTTP utilizzano verbi specifici che determinano il tipo di operazione che vogliamo effettuare. GET viene usato per recuperare informazioni dal server, ad esempio una pagina HTML o un'immagine. È il metodo più comune e quello che utilizziamo ogni volta che navighiamo su un sito web. POST invece serve per inviare dati al server, come quando compiliamo un form di registrazione o carichiamo un file. PUT permette di caricare o aggiornare una risorsa sul server in modo completo, mentre DELETE viene utilizzato per eliminare una risorsa esistente.

Altri verbi come HEAD, OPTIONS o PATCH sono meno comuni ma ugualmente importanti in scenari avanzati. HEAD funziona come GET ma restituisce solo gli header senza il corpo della risposta, utile per verificare se una risorsa esiste senza scaricarla. OPTIONS viene utilizzato per scoprire quali metodi HTTP sono supportati da una determinata risorsa. PATCH permette di effettuare modifiche parziali a una risorsa, a differenza di PUT che la sostituisce completamente.

Comprendere questi concetti è fondamentale: il server Python che andremo a costruire dovrà intercettare le richieste e, in base al verbo HTTP utilizzato, decidere come elaborarle e quale risposta fornire. Questa logica di routing e gestione delle richieste è alla base di qualsiasi framework web moderno.

Creare un Server Base con Python

Per iniziare il nostro viaggio nel mondo dello sviluppo web con Python, possiamo creare un server HTTP minimale che risponde con un semplice messaggio HTML. Questo primo esempio ci permette di capire come funziona il flusso di richiesta e risposta nella sua forma più essenziale, senza complicazioni aggiunte.

Python include nella sua libreria standard il modulo http.server, che fornisce le classi necessarie per creare un server HTTP funzionante con poche righe di codice. Non dobbiamo installare nulla: basta Python e un editor di testo. Questa è una delle bellezze di Python, la sua filosofia "batteries included" che mette a disposizione degli sviluppatori strumenti potenti già pronti all'uso.

from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(b'<html><body><h1>Ciao, mondo!</h1></body></html>')

PORT = 4200
httpd = ThreadingHTTPServer(('localhost', PORT), SimpleHTTPRequestHandler)
httpd.serve_forever()

Con queste poche righe di codice abbiamo un server web perfettamente funzionante. La classe ThreadingHTTPServer gestisce più richieste contemporaneamente utilizzando i thread, permettendo al nostro server di rispondere a più client simultaneamente senza bloccarsi. Questo è particolarmente importante in un ambiente di produzione, dove molti utenti potrebbero accedere al sito nello stesso momento.

La classe BaseHTTPRequestHandler fornisce la struttura base per gestire le richieste HTTP. Sovrascrivendo il metodo do_GET, definiamo cosa deve accadere quando il server riceve una richiesta GET. Prima inviamo il codice di stato 200 (che significa "OK, tutto è andato bene"), poi specifichiamo che il contenuto sarà di tipo HTML, chiudiamo gli header e infine scriviamo il contenuto HTML vero e proprio.

Salvando questo codice in un file Python ed eseguendolo, il server si avvierà sulla porta 4200. Visitando http://localhost:4200 con il browser, vedrete il messaggio "Ciao, mondo!" visualizzato come pagina web. Semplice, diretto, ma funzionale. Questo è il primo passo verso la creazione di applicazioni web più complesse.

Template Engine fatto a mano

Quando il progetto cresce e le pagine diventano più elaborate, mescolare Python e HTML diventa rapidamente difficile da gestire e mantenere. Il codice diventa confuso, le modifiche richiedono di toccare continuamente la logica del server, e la separazione tra presentazione e business logic diventa inesistente. Una soluzione elegante e pratica è creare un template engine rudimentale, che separi chiaramente la logica applicativa dalla presentazione visuale, senza dover installare librerie esterne.

Un template engine è essenzialmente un sistema che prende un file HTML con dei segnaposto e li sostituisce con valori dinamici provenienti dal nostro codice Python. Questo approccio è utilizzato da tutti i framework web moderni: Jinja2 in Flask, Django Template Language in Django, e così via. Creare una versione semplificata ci permette di capire come funzionano questi strumenti dietro le quinte.

L'idea è semplice: creiamo un file HTML separato con dei marcatori speciali (ad esempio {{nome}}) che verranno sostituiti con valori reali quando generiamo la risposta. Questo ci permette di modificare l'aspetto della pagina senza toccare il codice Python, e viceversa di modificare la logica senza dover riscrivere l'HTML.

Esempio di template (template.html):

<html>
<body>
<h1>Ciao, {{nome}}!</h1>
<p>Benvenuto nel nostro sito web!</p>
</body>
</html>

Server Python che usa il template:

from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler

def render_template(path, context):
    with open(path, 'r') as f:
        content = f.read()
    for key, value in context.items():
        content = content.replace('{{' + key + '}}', value)
    return content

class TemplateHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        content = render_template('template.html', {'nome': 'Luca'})
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(content.encode())

PORT = 4300
httpd = ThreadingHTTPServer(('localhost', PORT), TemplateHTTPRequestHandler)
httpd.serve_forever()

La funzione render_template è il cuore del nostro motore di template. Legge il file HTML, cerca tutti i segnaposto nel formato {{chiave}} e li sostituisce con i valori corrispondenti presi dal dizionario context. Questo approccio è estremamente flessibile: possiamo passare qualsiasi tipo di dato attraverso il context e renderlo nella pagina.

In questo modo possiamo modificare facilmente il contenuto della pagina senza toccare l'HTML direttamente nel codice Python. Il dizionario context permette di passare valori dinamici ai template, che possono provenire da database, calcoli, API esterne o qualsiasi altra fonte. Questo pattern di separazione delle responsabilità è uno dei pilastri dell'architettura software moderna.

Gestione dei Parametri URL

Un passo avanti fondamentale nello sviluppo web è la capacità di leggere e interpretare i parametri dall'URL. Questo meccanismo consente di creare pagine personalizzate in base a ciò che l'utente inserisce direttamente nell'indirizzo del browser, trasformando un sito statico in un'applicazione dinamica capace di rispondere a input specifici.

I parametri URL, anche chiamati query string, sono quella parte dell'indirizzo web che viene dopo il punto interrogativo. Per esempio, in http://example.com/page?nome=Luca&eta=30, la query string è nome=Luca&eta=30. Ogni parametro è formato da una coppia chiave-valore separate dal simbolo uguale, e più parametri sono separati dal simbolo &.

Python fornisce strumenti eccellenti per analizzare gli URL attraverso il modulo urllib.parse. Le funzioni urlparse e parse_qs ci permettono rispettivamente di dividere l'URL nelle sue componenti principali e di estrarre i parametri in un dizionario facilmente utilizzabile. Questo rende la gestione dei parametri incredibilmente semplice e intuitiva.

from urllib.parse import urlparse, parse_qs

class QueryHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        query = parse_qs(urlparse(self.path).query)
        nome = query.get('nome', ['ospite'])[0]
        response = f'<html><body><h1>Ciao, {nome}!</h1></body></html>'
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(response.encode())

PORT = 4400
httpd = ThreadingHTTPServer(('localhost', PORT), QueryHTTPRequestHandler)
httpd.serve_forever()

Visitando http://localhost:4400/?nome=Luca, la pagina mostrerà un saluto personalizzato con il nome specificato. Se non viene fornito alcun parametro, il codice utilizza un valore di default ('ospite') grazie al metodo get dei dizionari Python. Questo pattern è estremamente comune nelle applicazioni web: fornire valori di default sensati quando i parametri opzionali non sono presenti.

Questo principio è alla base di URL dinamici e query string, meccanismi che troviamo ovunque sul web moderno. Pensate ai risultati di ricerca su Google, ai filtri su siti di e-commerce, alle pagine di profilo sui social network: tutti questi utilizzano parametri URL per personalizzare il contenuto mostrato all'utente. Con poche righe di Python abbiamo replicato questa funzionalità fondamentale.

Gestione di Form HTML

Accettare dati dall'utente è un passo essenziale per rendere una pagina web veramente interattiva e trasformarla da semplice vetrina statica a applicazione dinamica capace di rispondere alle esigenze degli utenti. I form HTML rappresentano il metodo principale attraverso cui gli utenti comunicano informazioni al server, che si tratti di una registrazione, un login, una ricerca o qualsiasi altro tipo di input.

I form HTML inviano i dati utilizzando principalmente due metodi HTTP: GET e POST. Abbiamo già visto GET nella sezione precedente, dove i dati vengono passati attraverso l'URL come query string. POST invece invia i dati nel corpo della richiesta HTTP, rendendoli invisibili nell'URL e permettendo di trasmettere informazioni più sensibili e voluminose.

Il metodo POST è preferibile per form che contengono dati sensibili (come password), grandi quantità di informazioni, o quando l'operazione modifica lo stato del server (come creare un nuovo account o effettuare un acquisto). GET invece è ideale per ricerche e filtri, dove vogliamo che l'URL rifletta lo stato della pagina e possa essere facilmente condiviso o salvato nei preferiti.

class FormHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        form = '''<html><body>
        <form method="POST">
        Nome: <input name="nome">
        <input type="submit">
        </form></body></html>'''
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(form.encode())

    def do_POST(self):
        length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(length).decode()
        nome = parse_qs(post_data).get('nome', ['ospite'])[0]
        response = f'<html><body><h1>Ciao, {nome}!</h1></body></html>'
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(response.encode())

PORT = 4500
httpd = ThreadingHTTPServer(('localhost', PORT), FormHTTPRequestHandler)
httpd.serve_forever()

Questo esempio mostra come gestire completamente il ciclo di vita di un form HTML. Il metodo do_GET serve il form iniziale all'utente, presentando un semplice campo di input per il nome. Quando l'utente compila il form e preme il pulsante di invio, il browser invia una richiesta POST al server.

Il metodo do_POST entra in azione per elaborare i dati ricevuti. Prima leggiamo la lunghezza del contenuto dagli header HTTP, poi leggiamo esattamente quel numero di byte dal corpo della richiesta. Questi dati vengono poi decodificati da bytes a stringa e analizzati con parse_qs, che converte i dati del form in un dizionario facilmente utilizzabile. Infine, generiamo una risposta HTML personalizzata che saluta l'utente per nome.

Questo pattern di GET per mostrare il form e POST per elaborare i dati è uno standard del web development e lo ritroverete in praticamente ogni applicazione web esistente. Comprendere questo flusso è fondamentale per costruire applicazioni interattive.

Conclusioni

Creare una pagina web con Python utilizzando solo le librerie standard è non solo possibile, ma rappresenta un percorso didattico estremamente prezioso per comprendere i meccanismi fondamentali del web. Partendo da un server HTTP basilare fino ad arrivare a sistemi più sofisticati che gestiscono template, parametri URL e form HTML, abbiamo costruito passo dopo passo un'applicazione web funzionale e comprensibile.

Questo approccio bottom-up, che parte dalle fondamenta invece di affidarsi immediatamente a framework complessi, insegna concetti essenziali di HTTP, threading, gestione delle richieste e separazione tra logica e presentazione. Questi sono gli stessi principi che stanno alla base di framework come Flask, Django o FastAPI, ma comprenderli a questo livello fondamentale ci rende sviluppatori più consapevoli e capaci.

Lavorare con Python puro offre il vantaggio di avere il controllo completo sul flusso di dati e sul comportamento del server, eliminando la "magia" che spesso caratterizza i framework e permettendoci di capire esattamente cosa succede ad ogni step. Questa comprensione profonda dei meccanismi del web ci rende capaci di debuggare problemi complessi, ottimizzare le performance e fare scelte architetturali informate nei nostri progetti futuri.

Ogni esempio presentato in questa guida è stato progettato per essere breve, immediatamente eseguibile e modulare, consentendo di sperimentare e ampliare le funzionalità senza complicazioni. Potete prendere questi blocchi di codice come punto di partenza e costruirci sopra: aggiungere routing più sofisticati, implementare sessioni utente, gestire upload di file, integrare database o qualsiasi altra funzionalità di cui abbiate bisogno.

Il web development con Python è un campo vastissimo e affascinante, e questi primi passi rappresentano solo l'inizio di un viaggio che può portarvi a costruire applicazioni complesse e scalabili. La bellezza di partire dalle basi è che, quando passerete a framework più avanzati, capirete immediatamente cosa stanno facendo sotto il cofano e potrete sfruttarli al massimo delle loro potenzialità.