BaseHTTPServer - classi base per l'implementazione di server web

Scopo BaseHTTPServer include le classi che possono formare le basi per un server web.
Versione Python 1.4 e superiore

Il modulo BaseHTTPServer usa classi da SocketServer per creare le classi base per costruire dei server HTTP. HTTPServer può essere usato direttamente, mentre BaseHTTPRequestHandler è concepito per essere esteso per la gestione di ciascun metodo di protocollo (GET, POST, ecc.).

HTTP GET

Per aggiungere supporto ad un metodo HTTP nella propria classe per la gestione della richiesta, si implementa il metodo do_METHOD(), rimpiazzando METHOD con il nome del metodo HTTP. Ad esempio do_GET(), do_POST(), ecc. Per consistenza, il metodo non riceve parametri. Tutti i parametri per le richieste sono analizzati da BaseHTTPRequestHandler e conservati come attributi di istanza dell'istanza di richiesta.

Questo esempio di gestore di richiesta illustra come restituire una risposta al client ed alcuni degli attributi locali che possono essere utili nella costruzione della risposta:

from BaseHTTPServer import BaseHTTPRequestHandler
import urlparse

class GetHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        parsed_path = urlparse.urlparse(self.path)
        message_parts = [
                'VALORI DEL CLIENT:',
                'client_address=%s (%s)' % (self.client_address,
                                            self.address_string()),
                'command=%s' % self.command,
                'path=%s' % self.path,
                'real path=%s' % parsed_path.path,
                'query=%s' % parsed_path.query,
                'request_version=%s' % self.request_version,
                '',
                'VALORI DEL SERVER:',
                'server_version=%s' % self.server_version,
                'sys_version=%s' % self.sys_version,
                'protocol_version=%s' % self.protocol_version,
                '',                '',
                'INTESTAZIONI RICEVUTE:',
                ]
        for name, value in sorted(self.headers.items()):
            message_parts.append('%s=%s' % (name, value.rstrip()))
        message_parts.append('')
        message = '\r\n'.join(message_parts)
        self.send_response(200)
        self.end_headers()
        self.wfile.write(message)
        return

if __name__ == '__main__':
    from BaseHTTPServer import HTTPServer
    server = HTTPServer(('localhost', 8080), GetHandler)
    print 'Server in esecuzione, usare <Ctrl-C> per interrompere'
    server.serve_forever()

Il testo del messaggio viene assemblato e scritto in wfile, l'handle del file che incapsula la risposta del socket. Ogni risposta necessita di un codice di risposta, impostato tramite send_response(). Se viene usato un codice di errore (404, 501 ecc.), nell'intestazione viene incluso l'appropriato messaggio di errore predefinito. oppure può essere passato un messaggio con il codice di errore.

Per eseguire il gestore di richiesta in un server, lo si passa al costruttore di HTTPServer, come si vede nella porzione __main__ dello script di esempio.

Quindi si fa partire il server:

$ python BaseHTTPServer_GET.py
Server in esecuzione, usare  per interrompere

In un terminale separato si usa curl per accedervi:

$ curl -i http://localhost:8080/?foo=barHTTP/1.0
HTTP/1.0 200 OK
Server: BaseHTTP/0.3 Python/2.6.6
Date: Tue, 07 Dec 2010 20:52:01 GMT

VALORI DEL CLIENT:
client_address=('127.0.0.1', 37029) (localhost.localdomain)
command=GET
path=/?foo=barHTTP/1.0
real path=/
query=foo=barHTTP/1.0
request_version=HTTP/1.1

VALORI DEL SERVER:
server_version=BaseHTTP/0.3
sys_version=Python/2.6.6
protocol_version=HTTP/1.0


INTESTAZIONI RICEVUTE:
accept=*/*
host=localhost:8080
user-agent=curl/7.21.0 (i686-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18

HTTP POST

Il supporto per POST richiede un poco più di lavoro visto che la classe base non analizza i dati form. Il modulo cgi fornisce la classe FieldStorage che sa come analizzare il form, se viene fornita del corretto input.

from BaseHTTPServer import BaseHTTPRequestHandler
import cgi

class PostHandler(BaseHTTPRequestHandler):

    def do_POST(self):
        # Elabora i dati ricevuti nel form
        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={'REQUEST_METHOD':'POST',
                     'CONTENT_TYPE':self.headers['Content-Type'],
                     })

        # Begin the response
        self.send_response(200)
        self.end_headers()
        self.wfile.write('Client: %s\n' % str(self.client_address))
        self.wfile.write('User-agent: %s\n' % str(self.headers['user-agent']))
        self.wfile.write('Path: %s\n' % self.path)
        self.wfile.write('Dati form:\n')

        # Rimanda le informazioni su ci&ograve; che era stato passato nel form
        for field in form.keys():
            field_item = form[field]
            if field_item.filename:
                # Il campo contiene un file che &egrave; stato inviato
                file_data = field_item.file.read()
                file_len = len(file_data)
                del file_data
                self.wfile.write('\tInviato %s as "%s" (%d bytes)\n' % \
                        (field, field_item.filename, file_len))
            else:
                # Valori normali nel form
                self.wfile.write('\t%s=%s\n' % (field, form[field].value))
        return

if __name__ == '__main__':
    from BaseHTTPServer import HTTPServer
    server = HTTPServer(('localhost', 8080), PostHandler)
    print 'Server in esecuzione, usare <Ctrl-C> per interrompere'
    server.serve_forever()

curl può includere i dati form nel messaggio che invia al server. L'ultimo parametro, -F datafile@BaseHTTPServer_POST.py invia il contenuto del file BaseHTTPServer_POST.py per illustrare la lettura dei dati del file dal form.

$ curl http://localhost:8080/ -F name=robbpy -F foo=bar -F  datafile=@BaseHTTPServer_POST.py
Client: ('127.0.0.1', 50904)
User-agent: curl/7.21.0 (i686-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18
Path: /
Dati form:
	Inviato datafile as "BaseHTTPServer_POST.py" (1667 bytes)
	foo=bar
	name=robbpy

Threading e Forking

HTTPServer è una semplice classe derivata da SocketServer.TCPServer, e non usa thread o processi per gestire le richieste. Per aggiungere il threading od il forking, si crea una nuova classe che utilizza l'appropriato mix-in da SocketServer.

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
import threading

class Handler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        message =  threading.currentThread().getName()
        self.wfile.write(message)
        self.wfile.write('\n')
        return

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Gestisce le richieste in un thread separato."""

if __name__ == '__main__':
    server = ThreadedHTTPServer(('localhost', 8080), Handler)
    print 'Server in esecuzione, usare <Ctrl-C> per interrompere'
    server.serve_forever()

Ogni volta che perviene una richiesta, viene creato un nuovo thread o processo per gestirla.

$ curl http://localhost:8080/
Thread-1
$ curl http://localhost:8080/
Thread-2
$ curl http://localhost:8080/
Thread-3

Gestione Errori

La gestione degli errori è facilitata da send_error(). Basta passare l'appropriato codice di errore ed un messaggio di errore (opzionale), e l'intera risposta (compresi intestazioni, codici di stato e corpo) viene generata automaticamente.

from BaseHTTPServer import BaseHTTPRequestHandler

class ErrorHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_error(404)
        return

if __name__ == '__main__':
    from BaseHTTPServer import HTTPServer
    server = HTTPServer(('localhost', 8080), ErrorHandler)
    print 'Server in esecuzione, usare <Ctrl-C> per interrompere'
    server.serve_forever()

In questo caso viene sempre ritornato un errore 404.

$ curl -i http://localhost:8080/
HTTP/1.0 404 Not Found
Server: BaseHTTP/0.3 Python/2.6.6
Date: Tue, 07 Dec 2010 20:12:42 GMT
Content-Type: text/html
Connection: close

<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code 404.
<p>Message: Not Found.
<p>Error code explanation: 404 = Nothing matches the given URI.</p>
</body>

Impostazione delle Intestazioni

Il metodo send_header() aggiunge dati di intestazione alla risposta HTTP. Riceve due parametri, il nome dell'intestazione ed il valore.

from BaseHTTPServer import BaseHTTPRequestHandler
import urlparse
import time

class GetHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        self.send_header('Last-Modified', self.date_time_string(time.time()))
        self.end_headers()
        self.wfile.write('Corpo della risposta\n')
        return

if __name__ == '__main__':
    from BaseHTTPServer import HTTPServer
    server = HTTPServer(('localhost', 8080), GetHandler)
    print 'Server in esecuzione, usare <Ctrl-C> per interrompere'
    server.serve_forever()

Questo esempio imposta l'intestazione Last-modified alla data/ora corrente formattata secondo le specifiche RFC 2822.

$ curl -i http://localhost:8080/
HTTP/1.0 200 OK
Server: BaseHTTP/0.3 Python/2.6.6
Date: Tue, 07 Dec 2010 20:19:32 GMT
Last-Modified: Tue, 07 Dec 2010 20:19:32 GMT

Corpo della risposta

Vedere anche:

BaseHTTPServer
La documentazione della libreria standard per questo
SocketServer
Il modulo SocketServer fornisce la classe base che gestisce le connessioni raw socket.