WEB server / Application server
Sommario
Struttura base
Il server e' un'istanza della classe HTTPServer.
Si passa l'indirizzo+porta e la classe di gestione delle richieste derivata da BaseHTTPRequestHandler, infine si chiama il metodo serve_forever che rende funzionante e ricettivo il server.
I due metodi do_GET e do_POST del gestore rispondono alle request HTTP di tipo GET
(le variabili sono fornite assieme all'indirizzo) e POST (le variabili sono
fornite tramite un form HTML).
import BaseHTTPServer
#---------------------------------------------------------------------
class Gest(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
pass
def do_POST(self):
pass
#---------------------------------------------------------------------
ADDR = ("localhost", 80) # Indirizzo locale server
BaseHTTPServer.HTTPServer(ADDR, Gest).serve_forever()
Hello world!
Un po' di codice per ottenere una risposta minimale, l'hello world
del web server, qualunque richiesta di tipo GET
ottiene di ritorno sempre la stessa pagina contenuta nella stringa p.
import BaseHTTPServer
#---------------------------------------------------------------------
class Gest(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
p = """
<html>
<head></head>
<body>
<div style="text-align:center">
<h1>Hello world!<br>WEB Server Python here</h1>
</div>
</body>
</html>
"""
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
def do_POST(self):
pass
#---------------------------------------------------------------------
ADDR = ("localhost", 80) # Indirizzo locale server
BaseHTTPServer.HTTPServer(ADDR, Gest).serve_forever()
Pagine caricate da file
Con la richiesta HTTP arriva una stringa (riportata in self.path)
che contiene il percorso del file desiderato. Se non si specifica alcun
percorso/file viene ritornato comunque almeno uno slash "/".
Il percorso si intende relativo alla directory in cui si trova il server
e tutte le sottodirectory.
Una pagina puo' essere composta da piu' elementi (come le
immagini) e pertanto il browser puo' effettuare piu' richieste in sequenza
per recuperare una alla volta tutte le informazioni necessarie.
Per ogni tipo di file richiesto il server deve inviare nella risposta
il Content-Type e la Content-length relativi.
import BaseHTTPServer, urllib
#---------------------------------------------------------------------
class Gest(BaseHTTPServer.BaseHTTPRequestHandler):
tipi = { ".css": "text/css", ".gif": "image/gif",
".jpg": "image/jpg", ".png": "image/png",
"html": "text/html", ".htm": "text/html" }
def tipo(self, nomeFile):
a = nomeFile[-4:]
return (a in self.tipi) and self.tipi[a] or 'text/plain'
def scomponi_url(self):
a = urllib.unquote_plus(self.path.replace("&", "&"))
a = a.split("?", 1)
return a[0][1:].lower()
def do_GET(self):
nomeFile = self.scomponi_url()
p = file(nomeFile, "rb").read()
self.send_response(200)
self.send_header("Content-Type", self.tipo(nomeFile))
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
def do_POST(self):
pass
#---------------------------------------------------------------------
ADDR = ("localhost", 80) # Indirizzo locale server
BaseHTTPServer.HTTPServer(ADDR, Gest).serve_forever()
Errori e sicurezza
Il server precedente e' in grado di fornire pagine complete tramite
richieste GET ma presenta diversi problemi.
- Non e' in grado di gestire la condizione di
file mancanti o di nome errato.
- Non e' in grado di ricercare l'index.html qualora non si specifichi
nulla come percorso.
- Non si puo' specificare una directory di partenza a piacere per
il contenuto del sito.
- Non e' in grado di visualizzare un segnaposto per le immagini
non trovate o di nome errato.
Tutte queste cose vengono risolte con le modifiche seguenti, con la
costante WDIR si puo' anche specificare una diversa unita' disco. Tutti
i file si intendono relativi ad essa e alle eventuali sottodirectory.
In caso di file non recuperabile il server fornisce correttamente
l'errore 404.
Se non si specifica alcun file vengono cercati in sequenza index.htm e
index.html.
import BaseHTTPServer, urllib, os
WDIR = "C:/www/" # Directory ricerca file
ADDR = ("localhost", 80) # Indirizzo locale server
#---------------------------------------------------------------------
class Gest(BaseHTTPServer.BaseHTTPRequestHandler):
tipi = { ".css": "text/css", ".gif": "image/gif",
".jpg": "image/jpg", ".png": "image/png",
"html": "text/html", ".htm": "text/html" }
def tipo(self, nomeFile):
a = nomeFile[-4:]
return (a in self.tipi) and self.tipi[a] or 'text/plain'
def norm_path(self, p):
if not p:
if os.path.exists(WDIR + "index.htm"):
p = "index.htm"
else:
p = "index.html"
p = WDIR + p
if not os.path.exists(p):
if p[-4:] in [ ".gif", ".jpg", ".png" ]:
p = WDIR + "placeholder.jpg"
return p
def scomponi_url(self):
a = urllib.unquote_plus(self.path.replace("&", "&"))
a = a.split("?", 1)
p = a[0][1:].lower()
return self.norm_path(p)
def do_GET(self):
nomeFile = self.scomponi_url()
try:
p = file(nomeFile, "rb").read()
self.send_response(200)
self.send_header("Content-Type", self.tipo(nomeFile))
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
except IOError:
self.send_error(404, "File '%s' non trovato" % nomeFile)
def do_POST(self):
nomeFile = self.scomponi_url()
try:
p = file(nomeFile, "rb").read()
self.send_response(301)
self.send_header("Content-Type", self.tipo(nomeFile))
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
except IOError:
self.send_error(404, "File '%s' non trovato" % nomeFile)
#---------------------------------------------------------------------
BaseHTTPServer.HTTPServer(ADDR, Gest).serve_forever()
Accesso alle variabili fornite dal browser
Per la visualizzazione di semplici pagine statiche non e' necessario
altro, tuttavia per permettere al server di svolgere funzioni di elaborazione
piu' complesse e' necessario ricavare dalla request HTTP le variabili fornite
tramite il metodo GET o POST.
Il server seguente ricava (anche se ancora non le usa) le variabili in
entrambi i casi e le mette in un dizionario chiamato reqData.
import BaseHTTPServer, urllib, os
WDIR = "C:/www/" # Directory ricerca file
ADDR = ("localhost", 80) # Indirizzo locale server
#---------------------------------------------------------------------
class Gest(BaseHTTPServer.BaseHTTPRequestHandler):
tipi = { ".css": "text/css", ".gif": "image/gif",
".jpg": "image/jpg", ".png": "image/png",
"html": "text/html", ".htm": "text/html" }
def tipo(self, nomeFile):
a = nomeFile[-4:]
return (a in self.tipi) and self.tipi[a] or 'text/plain'
def scomponi_var(self, dati):
v = {}
for a in dati.split("&"):
b = a.split("=", 1)
v[b[0]] = (len(b) > 1) and b[1] or ""
return v
def leggi_postvars(self):
len_dati = int(self.headers.getheader('content-length'))
dati = urllib.unquote_plus(self.rfile.read(len_dati))
return self.scomponi_var(dati)
def norm_path(self, p):
if not p:
if os.path.exists(WDIR + "index.htm"):
p = "index.htm"
else:
p = "index.html"
p = WDIR + p
if not os.path.exists(p):
if p[-4:] in [ ".gif", ".jpg", ".png" ]:
p = WDIR + "placeholder.jpg"
return p
def scomponi_url(self):
a = urllib.unquote_plus(self.path.replace("&", "&"))
a = a.split("?", 1)
p = a[0][1:].lower()
getVars = (len(a) > 1) and self.scomponi_var(a[1]) or {}
return self.norm_path(p), getVars
def do_GET(self):
nomeFile, reqData = self.scomponi_url()
try:
p = file(nomeFile, "rb").read()
self.send_response(200)
self.send_header("Content-Type", self.tipo(nomeFile))
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
except IOError:
self.send_error(404, "File '%s' non trovato" % nomeFile)
def do_POST(self):
nomeFile, _ = self.scomponi_url()
reqData = self.leggi_postvars()
try:
p = file(nomeFile, "rb").read()
self.send_response(301)
self.send_header("Content-Type", self.tipo(nomeFile))
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
except IOError:
self.send_error(404, "File '%s' non trovato" % nomeFile)
#---------------------------------------------------------------------
BaseHTTPServer.HTTPServer(ADDR, Gest).serve_forever()
Pagine dinamiche e funzioni arbitrarie
Il server seguente e' un esempio "hello world" dinamico, tutti gli
url che terminano con .py causano la chiamata della funzione elabora
a cui vengono passati il path, le variabili della request e un dizionario
con le eventuali variabili dell'applicazione.
Questa funzione, se strutturata come macchina a stati finiti che viene
chiamata ad ogni richiesta del browser, puo' servire per realizzare
applicazioni web based che accedono ad ogni risorsa del computer.
La funzione deve resituire come risultato una pagina HTML.
#---------------------------------------------------------------------
# ESEMPIO RICHIAMO FUNZIONE DA WEB SERVER
#
# Di: Claudio Fin 2010
# Ultimo aggiornamento: 2/9/2010
#---------------------------------------------------------------------
import BaseHTTPServer, urllib, os, time
appData = {} # Dati applicazione
WDIR = "C:/www/" # Directory ricerca file
ADDR = ("localhost", 80) # Indirizzo locale web server
#---------------------------------------------------------------------
# FUNZIONE / APPLICAZIONE WEB
#---------------------------------------------------------------------
def elabora(nomeFile, reqData):
p = '''
<html>
<body style="background-color:white; margin:64px;
text-align:center; font-size:24px">
<h1>Hello world!<br>Dynamic contents here...</h1>
'''
p += '<h1>' + time.strftime("%H:%M",time.localtime()) + '</h1>\n'
p += '<h1>' + time.strftime("%d-%m-%Y",time.localtime()) +'</h1>\n'
p += '''
<a href="index.html"><b>INDEX</b></a>
<br><br><br>
'''
p += '<b>reqData = ' + repr(reqData) + '</b>\n'
p += '''
<br><br><br>
<form action="dinamico.py" method="POST">
<input type="text" value="" size=50 name="campone">
<input type="submit" value="Invio">
</form>
</body>
</html>
'''
return p
#---------------------------------------------------------------------
# WEB SERVER
#---------------------------------------------------------------------
class Gest(BaseHTTPServer.BaseHTTPRequestHandler):
tipi = { ".css": "text/css", ".gif": "image/gif",
".jpg": "image/jpg", ".png": "image/png",
"html": "text/html", ".htm": "text/html" }
def tipo(self, nomeFile):
a = nomeFile[-4:]
return (a in self.tipi) and self.tipi[a] or "text/plain"
def scomponi_var(self, dati):
v = {}
for a in dati.split("&"):
b = a.split("=", 1)
v[b[0]] = (len(b) > 1) and b[1] or ""
return v
def leggi_postvars(self):
len_dati = int(self.headers.getheader("content-length"))
dati = urllib.unquote_plus(self.rfile.read(len_dati))
return self.scomponi_var(dati)
def norm_path(self, p):
if not p:
if os.path.exists(WDIR + "index.htm"):
p = "index.htm"
else:
p = "index.html"
p = WDIR + p
if not os.path.exists(p):
if p[-4:] in [ ".gif", ".jpg", ".png" ]:
p = WDIR + "placeholder.jpg"
return p
def scomponi_url(self):
a = urllib.unquote_plus(self.path.replace("&", "&"))
a = a.split("?", 1)
p = a[0][1:].lower()
getVars = (len(a) > 1) and self.scomponi_var(a[1]) or {}
return self.norm_path(p), getVars
def do_GET(self):
nomeFile, reqData = self.scomponi_url()
try:
if nomeFile.endswith(".py"):
p = elabora(nomeFile, reqData)
self.send_response(200)
self.send_header("Content-Type", "text/html")
else:
p = file(nomeFile, "rb").read()
self.send_response(200)
self.send_header("Content-Type", self.tipo(nomeFile))
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
except IOError:
self.send_error(404, "File %s non trovato" % nomeFile)
def do_POST(self):
nomeFile, _ = self.scomponi_url()
try:
if nomeFile.endswith(".py"):
p = elabora(nomeFile, self.leggi_postvars())
self.send_response(200)
self.send_header("Content-Type", "text/html")
else:
p = file(nomeFile, "rb").read()
self.send_response(200)
self.send_header("Content-Type", self.tipo(nomeFile))
self.send_header("Content-length", str(len(p)))
self.end_headers()
self.wfile.write(p)
self.wfile.flush()
self.connection.shutdown(1)
except IOError:
self.send_error(404, "File %s non trovato" % nomeFile)
#---------------------------------------------------------------------
# MAIN
#---------------------------------------------------------------------
BaseHTTPServer.HTTPServer(ADDR, Gest).serve_forever()