Python HTTPServer

>>> LINK A PAGINA AGGIORNATA <<<

  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .
  • .

Importiamo le classi necessarie

Per realizzare un web server minimale sono necessarie le classi HTTPServer e BaseHTTPRequestHandler contenute nel modulo http.server (BaseHTTPServer in Python2).

from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler

 

Creiamo un handler vuoto e un server in ascolto

L’handler è la parte di programma che deve gestire la richiesta. È una classe che eredita da BaseHTTPRequestHandler e ne ridefinisce i metodi do_GET e do_POST.

Il server invece è un’istanza di HTTPServer, creata passando l’indirizzo locale (IP + porta in ascolto) e la classe handler. La stringa dell’IP può essere impostata uguale all’IP LAN della macchina su cui gira il server, oppure può essere lasciata vuota (l’IP viene preso automaticamente).

La porta di default su cui un WEB server attende richieste è la 80, ma per applicazioni particolari se ne possono usare altre, casi comuni sono 8000, 8080, 8800.

Il server normalmente può essere fermato solo con un’interruzione da tastiera (CTRL + C).


class Gestore(BaseHTTPRequestHandler):

    def do_GET(self):
        pass

    def do_POST(self):
        pass

#___________________________________________________________

indirizzo = '', 80
server = HTTPServer(indirizzo, Gestore)
try:
    server.serve_forever()
except KeyboardInterrupt:
    server.socket.close()

 

Oggetti disponibili nell’handler

Ad ogni request ricevuta dal server, viene chiamato il metodo do_GET oppure do_POST dell’handler. Questi sono gli oggetti principali con cui lavorare:

OGGETTO DESCRIZIONE
self.client_address Tupla (“ip”, porta)
self.path Stringa, es: “/pagine/file1.html”. Solo “/” se non si specifica niente nella richiesta. Da qui si leggono i dati ricevuti tramite una richiesta di tipo GET.
self.headers Tutti gli header inviati nella richiesta
self.server Istanza del server
self.rfile Stream in arrivo, da qui si leggono i dati ricevuti tramite una richiesta di tipo POST.
self.wfile Stream in uscita, qui si scrive il corpo della risposta (il testo HTML, altri file richiesti ecc)

Il server (istanza di HTTPServer) è costruito partendo dalla classe TCPServer del modulo socketserver (SocketServer in Python2). Quindi dispone di tutti gli attributi/metodi di TCPServer, ad esempio il metodo serve_forever per avviarlo.

Nota: se tra due applicazioni si vuole solo comunicare tramite TCP/IP scambiando messaggi con protocollo proprietario e non HTTP, allora va usato direttamente il modulo di livello più basso socketserver (istanziando un TCPServer). Il modulo http.server si trova infatti ad un livello di astrazione superiore, creato per gestire in modo semplificato le transazioni HTTP da/verso un browser.

 

Una risposta minimale

Prima l’esempio completo, poi la spiegazione…


from http.server  import HTTPServer
from http.server  import BaseHTTPRequestHandler
#___________________________________________________________

pagina = u'''<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
</head>
<body>
OK
</body>
</html>'''
#___________________________________________________________

def trasmetti(hdl, codice, tipo, contenuto):
    hdl.send_response(codice)
    hdl.send_header('Content-Type', tipo)
    hdl.send_header('Content-Length', len(contenuto))
    hdl.end_headers()
    hdl.wfile.write(contenuto)
    hdl.wfile.flush()
    hdl.connection.shutdown(1)
#___________________________________________________________

class Gestore(BaseHTTPRequestHandler):

    def do_GET(self):
        contenuto = pagina.encode('utf-8')
        tipo = 'text/html'
        trasmetti(self, 200, tipo, contenuto)

    def do_POST(self):
        pass
#___________________________________________________________

indirizzo = '', 80
server = HTTPServer(indirizzo, Gestore)
try:
    server.serve_forever()
except KeyboardInterrupt:
    server.socket.close()

Per dare al browser una risposta minimale (quella che comunemente viene chiamata “pagina WEB”) prepariamo una “pagina” minima ma completa in HTML5 sotto forma di stringa di testo.

È importante trattare sempre il testo in forma unicode in modo da poter rappresentare qualsiasi carattere. Questo è il default in Python3, mentre Python2 richiede di specificare esplicitamente il tipo di stringa anteponendo il carattere ‘u’ alle stringhe unicode.

Il prefisso ‘u’ è comunque accettato anche da Python3, quindi una stringa iniziante con ‘u’ va sempre bene ed evita ambiguità.

Il protocollo HTTP prevede che la risposta inizi con un codice di risposta, seguito da uno o più header che specificano informazioni sul contenuto, da una riga vuota che separa header da contenuto, ed infine dal contenuto vero e proprio (la pagina o qualsiasi altro dato richiesto).

L’invio del codice di risposta, degli header e della riga vuota, si ottengono con i metodi send_response, send_header, end_headers. Il contenuto vero e proprio invece si scrive sullo stream di uscita.

Per comodità si può creare una funzione ‘trasmetti’ con tutto il necessario per trasmettere la risposta. Il primo parametro è l’istanza dell’handler, il secondo il codice di risposta, il terzo il mimetype, il quarto i dati veri e propri.

Ogni contenuto va trasmesso sotto forma di byte, quindi il testo della pagina va codificato (encode). Va usato lo stesso encoding dichiarato nella sezione header della pagina HTML. In questo modo il browser che riceve i byte dei caratteri, li interpreta e visualizza sempre correttamente.

…e con questo abbiamo creato un WEB server che ad ogni richiesta di tipo GET risponde sempre con il testo “OK”.

Forse poco utile, ma necessario per vedere le basi e verificare il corretto funzionamento. Su qualche sistema può essere necessario avviare il server con i permessi di amministratore, ad esempio scrivendo a terminale:

sudo python3 webserver.py

 

Servire i file statici

Più interessante è un WEB server in grado di fornire i contenuti presenti in una specifica directory (e sottodirectory), pagine HTML, fogli di stile CSS, script Javascript, immagini ecc:


import os
from urllib.parse import unquote_plus
from http.server  import HTTPServer
from http.server  import BaseHTTPRequestHandler
BASEDIR = r'/home/a/Scrivania'
#___________________________________________________________

def trasmetti(hdl, codice, tipo, contenuto):
    hdl.send_response(codice)
    hdl.send_header('Content-Type', tipo)
    hdl.send_header('Content-Length', len(contenuto))
    hdl.end_headers()
    hdl.wfile.write(contenuto)
    hdl.wfile.flush()
    hdl.connection.shutdown(1)
#___________________________________________________________

TIPI = {
    '.css':  'text/css',
    '.gif':  'image/gif',
    '.jpg':  'image/jpg',
    '.png':  'image/png',
    '.ico':  'image/x-icon',
    '.html': 'text/html',
    '.htm':  'text/html',
    '.txt':  'text/plain',
    '.js':   'application/javascript'
}

def determina_tipo(risorsa):
    for estensione in TIPI:
        if risorsa.lower().endswith(estensione):
            return TIPI[estensione]
    return 'text/plain'
#___________________________________________________________

def invia_risorsa(hdl, risorsa):
    tipo = determina_tipo(risorsa)
    try:
        with open(BASEDIR + risorsa, 'rb') as fi:
            trasmetti(hdl, 200, tipo, fi.read())
    except IOError:
        hdl.send_error(500, 'Internal server error')
#___________________________________________________________

class Gestore(BaseHTTPRequestHandler):

    def do_GET(self):
        richiesta = unquote_plus(self.path)
        risorsa = richiesta.split('?', 1)[0]
        if os.path.isfile(BASEDIR + risorsa):
            invia_risorsa(self, risorsa)
        else:  
            self.send_error(
                404, '{} Not found'.format(risorsa))

    def do_POST(self):
        pass
#___________________________________________________________

indirizzo = '', 80
server = HTTPServer(indirizzo, Gestore)
try:
    server.serve_forever()
except KeyboardInterrupt:
    server.socket.close()

La stringa costante BASEDIR rappresenta la directory in cui il server deve cercare i file.

Nota: Sui sistemi Windows si usano i backslash ‘\’ per comporre i percorsi, e questi potrebbero essere interpretati da Python come sequenze di escape. Per scongiurare del tutto questa possibilità basta definire come “raw” tutte le stringhe che contengono percorsi, anteponendo il simbolo ‘r’.

La stringa self.path potrebbe presentarsi “quotata” con alcuni caratteri sostituiti da altri caratteri, inizianti con il simbolo ‘%’, ad esempio:

/archivio/1995/pag2.html?p=126&r=%22ab%20cd%22

Va quindi preventivamente “unquotata” con la funzione ‘unquote_plus’ del modulo urllib.parse (urllib in Python2).

Poi dobbiamo separare il percorso della risorsa da tutto quello che eventualmente si trova dopo il simbolo ‘?’ (che serve per la creazione dinamica dei contenuti, vedi articolo: Un WEB server più dinamico).

Se la risorsa corrisponde ad un nome di file presente, viene chiamata la funzione ‘invia_risorsa’, altrimenti viene trasmessa una pagina “non trovato” con codice errore 404.

Con la funzione ‘determina_tipo’, si determina il mimetype della risorsa grazie all’estensione del nome del file. Un tipo sconosciuto viene considerato text/plain.

 

 

 


(7/2018)


Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *