Welcome, guest | Sign In | My Account | Store | Cart

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

Python, 226 lines
  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'