For those who want to start dynamic web programming, but don't know what to choose among the many Python web frameworks, this program might be a good starting point
ScriptServer is a minimalist application server, handling both GET and POST requests, including multipart/form-data for file uploads, HTTP redirections, and with an in-memory session management. It can run Python scripts and template files using the standard string substitution format
The scripts are run in the same process as the server, avoiding the CGI overhead. The environment variables are provided in the namespace where the script runs
To start the server, run
python ScriptServer.py
In your web browser, enter http://localhost, this will show you a listing of the directory. Add the scripts in the same directory as ScriptServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | """Script server based on SimpleHTTPServer
Handles GET and POST requests, in-memory session management,
HTTP redirection
Python scripts are executed in a namespace made of :
- request : for the data received from the query string or the request body.
Calling 'http://host/myScript.py?foo=bar' will make
request = {'foo':['bar']} available in the namespace of myScript
- headers : the http request headers
- resp_headers : the http response headers
- Session() : a function returning the session object
- HTTP_REDIRECTION : an exception to raise if the script wants to redirect
to a specified URL (raise HTTP_REDIRECTION, url)
A simple templating system is provided, using the Python string substitution
mechanism introduced in Python 2.4 (syntax $name). Template files must have
the extension .tpl
Hello world programs : will print "Hello world !" if called with the query
string ?name=world
- hello.py (Python script) [ http://localhost/hello.py?name=world ]
print "Hello",request['name'][0],"!"
- hello.tpl (template) [ http://localhost/hello.tpl?name=world ]
Hello $name !
Other extensions can be handled by adding methods self.run_(extension)
"""
import sys
import os
import string
import cStringIO
import random
import cgi
import select
import SimpleHTTPServer
import Cookie
chars = string.ascii_letters + string.digits
sessionDict = {} # dictionary mapping session id's to session objects
class SessionElement(object):
"""Arbitrary objects, referenced by the session id"""
pass
def generateRandom(length):
"""Return a random string of specified length (used for session id's)"""
return ''.join([random.choice(chars) for i in range(length)])
class HTTP_REDIRECTION(Exception):
pass
class ScriptRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""One instance of this class is created for each HTTP request"""
def do_GET(self):
"""Begin serving a GET request"""
# build self.body from the query string
self.body = {}
if self.path.find('?')>-1:
qs = self.path.split('?',1)[1]
self.body = cgi.parse_qs(qs, keep_blank_values=1)
self.handle_data()
def do_POST(self):
"""Begin serving a POST request. The request data is readable
on a file-like object called self.rfile"""
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
length = int(self.headers.getheader('content-length'))
if ctype == 'multipart/form-data':
self.body = cgi.parse_multipart(self.rfile, pdict)
elif ctype == 'application/x-www-form-urlencoded':
qs = self.rfile.read(length)
self.body = cgi.parse_qs(qs, keep_blank_values=1)
else:
self.body = {} # Unknown content-type
# some browsers send 2 more bytes...
[ready_to_read,x,y] = select.select([self.connection],[],[],0)
if ready_to_read:
self.rfile.read(2)
self.handle_data()
def handle_data(self):
"""Process the data received"""
self.resp_headers = {"Content-type":'text/html'} # default
self.cookie=Cookie.SimpleCookie()
if self.headers.has_key('cookie'):
self.cookie=Cookie.SimpleCookie(self.headers.getheader("cookie"))
path = self.get_file() # return a file name or None
if os.path.isdir(path):
# list directory
dir_list = self.list_directory(path)
self.copyfile(dir_list, self.wfile)
return
ext = os.path.splitext(path)[1].lower()
if len(ext)>1 and hasattr(self,"run_%s" %ext[1:]):
# if run_some_extension() exists
exec ("self.run_%s(path)" %ext[1:])
else:
# other files
ctype = self.guess_type(path)
if ctype.startswith('text/'):
mode = 'r'
else:
mode = 'rb'
try:
f = open(path,mode)
self.resp_headers['Content-type'] = ctype
self.resp_headers['Content-length'] = str(os.fstat(f.fileno())[6])
self.done(200,f)
except IOError:
self.send_error(404, "File not found")
def done(self, code, infile):
"""Send response, cookies, response headers
and the data read from infile"""
self.send_response(code)
for morsel in self.cookie.values():
self.send_header('Set-Cookie', morsel.output(header='').lstrip())
for (k,v) in self.resp_headers.items():
self.send_header(k,v)
self.end_headers()
infile.seek(0)
self.copyfile(infile, self.wfile)
def get_file(self):
"""Set the Content-type header and return the file open
for reading, or None"""
path = self.path
if path.find('?')>1:
# remove query string, otherwise the file will not be found
path = path.split('?',1)[0]
path = self.translate_path(path)
if os.path.isdir(path):
for index in "index.html", "index.htm":
index = os.path.join(path, index)
if os.path.exists(index):
path = index
break
return path
def run_py(self, script):
"""Run a Python script"""
# redirect standard output so that the "print" statements
# in the script will be sent to the web browser
sys.stdout = cStringIO.StringIO()
# build the namespace in which the script will be run
namespace = {'request':self.body, 'headers' : self.headers,
'resp_headers':self.resp_headers, 'Session':self.Session,
'HTTP_REDIRECTION':HTTP_REDIRECTION}
try:
execfile (script,namespace)
except HTTP_REDIRECTION,url:
self.resp_headers['Location'] = url
self.done(301,cStringIO.StringIO())
except:
# print a traceback
# first reset the output stream
sys.stdout = cStringIO.StringIO()
exc_type,exc_value,tb=sys.exc_info()
msg = exc_value.args[0]
if tb.tb_next is None: # errors (detected by the parser)
line = exc_value.lineno
text = exc_value.text
else: # exceptions
line = tb.tb_next.tb_lineno
text = open(script).readlines()[line-1]
print '%s in file %s : %s' %(exc_type.__name__,
os.path.basename(script), cgi.escape(msg))
print '<br>Line %s' %line
print '<br><pre><b>%s</b></pre>' %cgi.escape(text)
self.resp_headers['Content-length'] = sys.stdout.tell()
self.done(200,sys.stdout)
def run_tpl(self,script):
"""Templating system with the string substitution syntax
introduced in Python 2.4"""
# values must be strings, not lists
dic = dict([ (k,v[0]) for k,v in self.body.items() ])
# first check if the string.Template class is available
if hasattr(string,"Template"): # Python 2.4 or above
try:
data = string.Template(open(script).read()).substitute(dic)
except:
exc_type,exc_value,tb=sys.exc_info()
msg = exc_value.args[0]
data = '%s in file %s : %s' \
%(exc_type.__name__,os.path.basename(script),
cgi.escape(msg))
else:
data = "Unable to handle this syntax for " + \
"string substitution. Python version must be 2.4 or above"
self.resp_headers['Content-length'] = len(data)
self.done(200,cStringIO.StringIO(data))
def Session(self):
"""Session management
If the client has sent a cookie named sessionId, take its value and
return the corresponding SessionElement objet, stored in
sessionDict
Otherwise create a new SessionElement objet and generate a random
8-letters value sent back to the client as the value for a cookie
called sessionId"""
if self.cookie.has_key("sessionId"):
sessionId=self.cookie["sessionId"].value
else:
sessionId=generateRandom(8)
self.cookie["sessionId"]=sessionId
try:
sessionObject = sessionDict[sessionId]
except KeyError:
sessionObject = SessionElement()
sessionDict[sessionId] = sessionObject
return sessionObject
if __name__=="__main__":
# launch the server on the specified port
import SocketServer
port = 80
s=SocketServer.TCPServer(('',port),ScriptRequestHandler)
print "ScriptServer running on port %s" %port
s.serve_forever()
|
A simple example of how to use the session management and the exception HTTP_REDIRECTION
====================
home page : index.py
print "<h3>Simple portal</h3>"
so = Session()
if hasattr(so,'user'):
print "User :", so.user
print '<br><a href="logout.py">Logout</a>'
else:
print '<a href="login.html">Login</a>'
print "<p>Your content here..."
=======================
login page : login.html
<form action="login.py">
User <input name="user">
<input type="submit">
</form>
=======================
login script : login.py
# set the attribute 'user' of the session object
so = Session()
so.user = request['user'][0]
# redirect to the home page
raise HTTP_REDIRECTION,"index.py"
=========================
logout script : logout.py
# logout : remove the attribute 'user' of the session object
delattr(Session(),'user')
# redirect to the home page
raise HTTP_REDIRECTION,'index.py'