Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Creare un server HTTP con Python

Sfruttando il modulo http.server messo a disposizione di Python, è possibile implementare un semplice server HTTP in pochissimo tempo: ecco come.
Sfruttando il modulo http.server messo a disposizione di Python, è possibile implementare un semplice server HTTP in pochissimo tempo: ecco come.
Link copiato negli appunti

Essere in grado di implementare un server HTTP è una skill ormai spendibile in quasi qualsiasi contesto. Non è solo utile per chi sviluppa le più classiche applicazioni web, ma anche a chiunque abbia bisogno di far comunicare un dispositivo (ad esempio uno smartphone, o un Raspberry Pi) con un server molto semplice, che fornisca anche solo informazioni di controllo e molto basilari. Questo tipo di scenario è particolarmente comune nei contesti in cui si sviluppano soluzioni prototipali, come l'ambito dei maker o quello della ricerca. In questi contesti trova spesso applicazione il linguaggio di programmazione Python, nato con lo scopo di velocizzare lo sviluppo del codice, rendendelo altresì snello ed elegante.

In questo articolo, vedremo quindi come realizzare un semplice server HTTP con Python. Scopriremo come ciò richieda poco tempo e poche righe di codice, due requisiti essenziali per chi vuole sviluppare software prototipali ma comunque performanti.

Prima di iniziare, è bene ricordare che chiunque abbia bisogno di conoscere (o semplicamente di rinfrescare) le basi di Python, può fare riferimento alla guida a Python di HTML.it. La guida, così come questo articolo, fa riferimento a Python 3; percui quanto vedremo è applicabile a qualsiasi nuovo progetto.

Il codice Python HTTP Server

Vediamo subito il codice di cui abbiamo bisogno, e che poi andremo a commentare:

#!/usr/bin/env python
from http.server import BaseHTTPRequestHandler, HTTPServer
# Creiamo la classe che riceverà e risponderà alla richieste HTTP
class testHTTPServer_RequestHandler(BaseHTTPRequestHandler):
# Implementiamo il metodo che risponde alle richieste GET
def do_GET(self):
# Specifichiamo il codice di risposta
self.send_response(200)
# Specifichiamo uno o più header
self.send_header('Content-type','text/html')
self.end_headers()
# Specifichiamo il messaggio che costituirà il corpo della risposta
message = "Hello world!"
self.wfile.write(bytes(message, "utf8"))
return
def run():
print('Avvio del server...')
# Specifichiamo le impostazioni del server
# Scegliamo la porta 8081 (per la porta 80 sono necessari i permessi di root)
server_address = ('127.0.0.1', 8081)
httpd = HTTPServer(server_address, testHTTPServer_RequestHandler)
print('Server in esecuzione...')
httpd.serve_forever()
run()

Lo script inizia importando le classi HTTPServer e BaseHTTPRequestHandler dal modulo http.server. Quest'ultimo include anche le funzionalità che, su Python 2, venivano implementate dal modulo SimpleHTTPServer.

Dopo l'import, implementiamo una classe che chiamiamo testHTTPServer_RequestHandler, e che estende la classe BaseHTTPRequestHandler. Quest'ultima è in grado di intercettare tutte le richieste HTTP provenienti dall'esterno, mappandole su alcuni metodi "speciali". In realtà, le funzionalità messe a disposizione da http.server sono relativamente limitate, e ci permettono di gestire solo le richieste di tipo più comune, ovvero GET, POST ed HEAD. Nell'esempio, abbiamo implementato soltanto il metodo do_GET(), che viene eseguito tutte le volte che il server riceve una richiesta HTTP di tipo GET. Per rispondere a richieste HTTP di tipo POST ed HEAD, sarà necessario implementare rispettivamente i metodi do_POST() e do_HEAD().

Guardando ora all'implementazione del metodo do_GET(), vediamo alcuni metodi significativi, ereditati da BaseHTTPRequestHandler. Utilizzando self.send_response(200), specifichiamo il codice di risposta che sarà inserito nell'header della risposta del server (in questo caso, 200 significa che la risposta sarà positiva, in ogni caso). Possiamo poi aggiungere altri header, come il Content-type dell'esempio; se ne aggiungiamo diversi, l'unica cosa da ricordare è la necessaria esecuzione di self.end_headers() al termine dell'esecuzione dei vari self.send_header(). Infine, per definire il corpo vero e proprio della risposta, utilizziamo il metodo self.wfile.write(), a cui dovremo passare un array di byte. Possiamo convertire una qualsiasi stringa come mostrato nell'esempio, cioè passandola come argomento a bytes(), unitamente alla codifica (in questo caso utf8).

Definita la classe, la funzione run() si limita poi a creare un'istanza di HTTPServer, specificando l'IP e la porta. Si noti che, nell'esempio, abbiamo utilizzato la porta 8081 poiché, per la porta 80 (quella tradizionalmente associata ad ogni server HTTP), è necessario utilizzare i permessi di root. Infine, con il metodo httpd.serve_forever() il nostro server rimarrà in ascolto di richieste HTTP finché non ne forzeremo l'arresto.

Possiamo verificare il funzionamento del nostro server, aprendo un browser e digitando, nella barra dell'indirizzo, l'URL http://127.0.0.1:8081. Se tutto è stato eseguito correttamente otterremo un risultato simile al seguente:

Figura 1. Schermata del browser, che mostra la risposta del server HTTP (click per ingrandire)

Schermata del browser, che mostra la risposta del server HTTP

Gestire i dati della richiesta Python web Server

Un'altra cosa che è possibile fare (e che non è stata analizzata nell'esempio precedente) è la lettura dei dati inviati al server insieme alla richiesta. Per capire il funzionamento di questo meccanismo, diamo un'occhiata alla seguente implementazione di un metodo do_POST():

def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write("<html><body><h1>POST!</h1></body></html>")

Sfruttando l'array self.headers[] possiamo innanzitutto ottenere la dimensione dei dati, che utilizziamo poi per leggere il contenuto della richiesta, sfruttando il metodo self.rfile.read() (a cui passiamo proprio la dimensione ottenuta precedentemente). Nello snippet appena visto, in realtà, non usiamo in nessun modo i dati ottenuti (quelli memorizzati nella variabile post_data); un ottimo esercizio può consistere nella modifica di questo codice, tentando di utilizzare i dati ottenuti tramite POST in qualsiasi modo che esemplifichi un caso d'uso reale.

I dati che riceviamo, invece, nel caso di una richiesta GET, potrebbero essere memorizzati all'interno di una query string, in coda all'URL. Per ottenerla, abbiamo bisogno di effettuare il parsing dell'URL, e a tale scope utilizziamo il modulo urlparse. Dopo averlo importato come segue:

import urlparse

possiamo inserire il seguente codice all'interno del nostro metodo do_GET():

from urlparse import urlparse, parse_qs
query_components = parse_qs(urlparse(self.path).query)

Con questa sintassi, possiamo ottenere i dati dalla query string come mostrato nel seguente snippet:

# Query string: ?foo=bar&test=prova
query_components["foo"] # bar
query_components["test"] # prova

Note conclusive sul modulo http.server

L'uso del modulo http.server è comodo se vogliamo realizzare un semplice server HTTP in pochissimo tempo. Va detto, però, che questo modulo non è pensato per uso in produzione (sebbene possa tornare utile in fase di prototipizzazione, come accennato all'inizio di questo articolo). In ogni caso, ogni ulteriore dettaglio sull'uso di quanto visto finora può essere reperito facendo riferimento alla documentazione ufficiale, fornita dalla PSF.

Ti consigliamo anche