Dies ist die Support Website des Buches:

Das Python Praxisbuch
Der große Profi-Leitfaden für Programmierer
Farid Hajji
Addison Wesley / Pearson Education
ISBN 978-3-8273-2543-3 (Sep 2008), 1298 Seiten.

15. Webprogrammierung und Web Frameworks

Webserver in Python

Webserver aus der Python Standard Library

BaseHTTPServer

#!/usr/bin/env python
# basehttpserver.py -- a basic HTTP server that doesn't understand GET, POST...

import BaseHTTPServer

server_address = ('', 9090)
handler_class  = BaseHTTPServer.BaseHTTPRequestHandler
server_class   = BaseHTTPServer.HTTPServer

server = server_class(server_address, handler_class)
server.serve_forever()

basehttpserver.py

#!/usr/bin/env python
# basehttpserver2.py -- a basic HTTP server without GET method.

import BaseHTTPServer

class MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        print "Got GET Request from", self.client_address
        self.wfile.write('Sorry, I do not speak HTTP. Go away.\r\n')

server_address = ('', 9090)
handler_class  = MyHandler
server_class   = BaseHTTPServer.HTTPServer

server = server_class(server_address, handler_class)
server.serve_forever()

basehttpserver2.py

#!/usr/bin/env python
# basehttpcalc.py -- a basic HTTP server / calculator

import BaseHTTPServer

class CalcHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        path = self.path

        lst = path.split('/')
        if len(lst) != 4:
            self.send_response(403)
            self.end_headers()
            self.wfile.write('Illegal syntax. '
                             'Use /{add,sub,mul,div}/num1/num2'
                             '\r\n')
            return
        
        dummy, op, arg1, arg2 = lst

        if op not in ('add', 'sub', 'mul', 'div'):
            self.send_response(403)
            self.end_headers()
            self.wfile.write('Illegal operation: ' + op + '\r\n')
            return
        
        try:
            numarg1 = float(arg1)
            numarg2 = float(arg2)
        except ValueError:
            self.send_response(403)
            self.end_headers()
            self.wfile.write('Numerical arguments expected' + '\r\n')
            return

        if op == 'add': result = numarg1 + numarg2
        elif op == 'sub': result = numarg1 - numarg2
        elif op == 'mul': result = numarg1 * numarg2
        elif op == 'div':
            if numarg2 == 0: result = 'NaN'
            else:            result = numarg1 / numarg2
        
        self.send_response(200)
        self.end_headers()
        self.wfile.write(str(result) + '\r\n')
            
def run_server(port=9090):
    server_class   = BaseHTTPServer.HTTPServer
    server_address = ('', port)
    handler_class  = CalcHandler
    
    server = server_class(server_address, handler_class)
    server.serve_forever()

if __name__ == '__main__':
    run_server()

basehttpcalc.py

SimpleHTTPServer

#!/usr/bin/env python
# simplehttpserver.py -- a simple HTTP server to expose a directory hierarchy

import BaseHTTPServer
import SimpleHTTPServer
            
def run_server(port=9090):
    server_class   = BaseHTTPServer.HTTPServer
    handler_class  = SimpleHTTPServer.SimpleHTTPRequestHandler
    server_address = ('', port)
    
    server = server_class(server_address, handler_class)
    server.serve_forever()

if __name__ == '__main__':
    run_server()

simplehttpserver.py

CGIHTTPServer

#!/usr/bin/env python
# cgihttpserver.py -- a CGI-enabled HTTP server to expose a directory hierarchy

import BaseHTTPServer
import CGIHTTPServer
            
def run_server(port=9090):
    server_class   = BaseHTTPServer.HTTPServer
    handler_class  = CGIHTTPServer.CGIHTTPRequestHandler
    handler_class.cgi_directories = ['/cgi-bin']
    server_address = ('', port)
    
    server = server_class(server_address, handler_class)
    server.serve_forever()

if __name__ == '__main__':
    run_server()

cgihttpserver.py

#!/usr/bin/env python
# cgiprintenv.py -- Display CGI Environment Variables.

import os
from sys import stdout

stdout.write("Content-type: text/plain\r\n\r\n")

for key in sorted(os.environ.keys()):
    print "%s=%s" % (key, os.environ[key])

cgiprintenv.py

wsgiref.simple_server

Eine einfache WSGI-Anwendung:

def demo_app(environ,start_response):
    from StringIO import StringIO
    stdout = StringIO()
    print >>stdout, "Hello world!"
    print >>stdout
    h = environ.items(); h.sort()
    for k,v in h:
        print >>stdout, k,'=',`v`
    start_response("200 OK", [('Content-Type','text/plain')])
    return [stdout.getvalue()]
#!/usr/bin/env python
# wsgidemo.py -- Running the WSGI-Demo within a WSGI-enabled webserver
# From: Python Library Reference, 18.4.3 wsgiref.simple_server, make_server()

from wsgiref.simple_server import make_server, demo_app

server = make_server('', 9090, demo_app)
server.serve_forever()

wsgidemo.py

#!/usr/bin/env python
# wsgicalc.py -- A web calculator as WSGI application

def webcalc(environ, start_response):
    "A web calculator as WSGI application"
    from cStringIO import StringIO
    out = StringIO()

    def send_result(result):
        print >>out, result + "\r\n"
        start_response("200 OK", [('Content-Type', 'text/plain')])

    path = environ.get('PATH_INFO', None)
    if path is None:
        send_result("Usage: /{add,sub,mul,div}/num1/num2")
        return [out.getvalue()]

    lst = path.split('/')
    if len(lst) != 4:
        send_result("Invalid syntax. Use /op/arg1/arg2")
        return [out.getvalue()]
    
    dummy, op, arg1, arg2 = lst
    if op not in ('add', 'sub', 'mul', 'div'):
        send_result("Invalid operator. Use one of add, sub, mul, div.")
        return [out.getvalue()]

    try:
        numarg1 = float(arg1)
        numarg2 = float(arg2)
    except ValueError:
        send_result("Invalid numerical argument.")
        return [out.getvalue()]

    if op == 'add': result = numarg1 + numarg2
    elif op == 'sub': result = numarg1 - numarg2
    elif op == 'mul': result = numarg1 * numarg2
    elif op == 'div':
        if numarg2 == 0: result = 'N/A'
        else:            result = numarg1 / numarg2
    send_result(str(result))
    return [out.getvalue()]

def run(port):
    "Start the WSGI server"
    from wsgiref.simple_server import make_server
    server = make_server('', port, webcalc)
    server.serve_forever()

if __name__ == '__main__':
    run(9090)

wsgicalc.py

Webserver aus Drittanbietermodulen

Webserver mit CherryPy

URLs:

Screenshots:

#!/usr/bin/env python
# cherrypyhello.py -- A simple hello, world with CherryPy

import cherrypy

class Hello(object):
    @cherrypy.expose
    def index(self):
        return "Hello, CherryPy World"

if __name__ == '__main__':
    hello = Hello()
    cherrypy.quickstart(hello, config='cherrypy.cfg')

cherrypyhello.py

#!/usr/bin/env python
# cherrypymapper.py -- URL-mapping to methodes with CherryPy

import cherrypy

class URLMapper(object):
    def foo(self):     return "This is foo()"
    def bar(self):     return "This is bar()"
    def index(self):   return "This is index()"
    def default(self): return "This is default()"

    foo.exposed = True
    bar.exposed = True
    index.exposed = True
    default.exposed = True

if __name__ == '__main__':
    mapper = URLMapper()
    cherrypy.quickstart(mapper, config='cherrypy.cfg')

cherrypymapper.py

#!/usr/bin/env python
# cherrypymapper2.py -- enhanced URL-mapping to methods with CherryPy

import cherrypy

class URLMapper2(object):
    def foo(self, *args):     return "This is foo(%s)\r\n"     % str(args)
    def bar(self, *args):     return "This is bar(%s)\r\n"     % str(args)
    def index(self, *args):   return "This is index(%s)\r\n"   % str(args)
    def default(self, *args): return "This is default(%s)\r\n" % str(args)

    foo.exposed = True
    bar.exposed = True
    index.exposed = True
    default.exposed = True

if __name__ == '__main__':
    mapper = URLMapper2()
    cherrypy.quickstart(mapper, config='cherrypy.cfg')

cherrypymapper2.py

#!/usr/bin/env python
# cherrypysite.py -- a web site with many different parts

import cherrypy

class Rootsite(object):
    @cherrypy.expose
    def index(self): return "Rootsite.index()\r\n"

    @cherrypy.expose
    def default(self, *args): return "Rootsite.default(%s)\r\n" % str(args)

class Forum(object):
    @cherrypy.expose
    def index(self): return "Forum.index()\r\n"
    
    @cherrypy.expose    
    def default(self, *args): return "Forum.default(%s)\r\n" % str(args)

    @cherrypy.expose
    def admin(self, *args): return "Forum.admin(%s)\r\n" % str(args)

class Archive(object):
    @cherrypy.expose
    def index(self): return "Archive.index()\r\n"

    @cherrypy.expose
    def default(self, *args): return "Archive.default(%s)\r\n" % str(args)

if __name__ == '__main__':
    rootsite = Rootsite()
    rootsite.talkback = Forum()
    rootsite.newsroom = Archive()
    cherrypy.quickstart(rootsite, config='cherrypy.cfg')

cherrypysite.py

#!/usr/bin/env python
# cherrypyform.py -- read data from a form

import cherrypy

class FeedbackForm(object):
    @cherrypy.expose
    def feedback(self, username=None, email=None):
        if username is None or email is None:
            return self.send_form()
        else:
            return "Got username: %s, Email: %s" % (username, email)

    def send_form(self):
        return '''<div>
  <form action="/feedback" method="POST">
    <p>Username: <input type="text" name="username" size="40"/></p>
    <p>Email:    <input type="text" name="email" size="40"/></p>
    <p><input type="submit" value="Send"/>
       <input type="reset" value="Clear"/>
    </p>
  </form>
</div>'''

if __name__ == '__main__':
    feedback = FeedbackForm()
    cherrypy.quickstart(feedback, config='cherrypy.cfg')

cherrypyform.py

#!/usr/bin/env python
# cherrypycalc.py -- web calculator using CherryPy

import cherrypy

class Calculator(object):
    @cherrypy.expose
    def add(self, arg1, arg2):
        return self.calculate('add', arg1, arg2)

    @cherrypy.expose
    def sub(self, arg1, arg2):
        return self.calculate('sub', arg1, arg2)

    @cherrypy.expose
    def mul(self, arg1, arg2):
        return self.calculate('mul', arg1, arg2)

    @cherrypy.expose
    def div(self, arg1, arg2):
        return self.calculate('div', arg1, arg2)

    @cherrypy.expose
    def default(self, *args):
        return "Invalid operator. Use one of 'add', 'sub', 'mul', or 'div'"

    def calculate(self, op, arg1, arg2):
        # if op not in ('add', 'sub', 'mul', 'div'):
        #     return "Invalid operator. Use 'add', 'sub', 'mul', or 'div'"
        result = 'No result yet'
        try:
            num1 = float(arg1)
            num2 = float(arg2)
            if op == 'add': result = num1 + num2
            elif op == 'sub': result = num1 - num2
            elif op == 'mul': result = num1 * num2
            elif op == 'div':
                if num2 == 0: result = 'NaN'
                else        : result = num1 / num2
        except ValueError:
            result = "Invalid operand. Use numerical values only."
        finally:
            return str(result)

if __name__ == '__main__':
    calc = Calculator()
    cherrypy.quickstart(calc, config='cherrypy.cfg')

cherrypycalc.py

Twisted.Web Server

URLs:

#!/usr/bin/env python
# twistedhelloworld.py -- A simple hello, world Twisted.Web server.
# From: Twisted.Web HowTo "Configuring and Using the Twisted.Web server"

from twisted.web import server, resource
from twisted.internet import reactor

class HelloWorld(resource.Resource):
    
    isLeaf = True
    
    def render_GET(self, request):
        return "<html>Hello, world.</html>"

if __name__ == '__main__':
    site = server.Site(HelloWorld())
    reactor.listenTCP(9090, site)
    reactor.run()

twistedhelloworld.py

#!/usr/bin/env python
# twistedurl1.py -- A Twisted.Web with two attached resources.

from twisted.web import server, resource
from twisted.internet import reactor

class RootResource(resource.Resource):
    def getChild(self, name, request):
        if name == '':
            return self
        else:
            return resource.Resource.getChild(self, name, request)
    
    def render_GET(self, request):
        return "I am the Root resource.\r\n"

class ForumResource(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return "I am a Forum resource. request.postpath=%r\r\n" % \
               (request.postpath,)

class ArchiveResource(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return "I am an Archive resource. request.postpath=%r\r\n" % \
               (request.postpath,)

if __name__ == '__main__':
    root = RootResource()
    root.putChild('talkback', ForumResource())
    root.putChild('newsroom', ArchiveResource())
    
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedurl1.py

#!/usr/bin/env python
# twistedurl2.py -- A Twisted.Web with two attached resources.

from twisted.web import server, resource
from twisted.internet import reactor

class DynamicResource(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return "I am a dynamic resource.\r\n" \
               "request.prepath=%r, request.postpath=%r\r\n" % \
               (request.prepath, request.postpath)

if __name__ == '__main__':
    root = resource.Resource()
    root.putChild('talkback', DynamicResource())
    root.putChild('newsroom', DynamicResource())
    
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedurl2.py

#!/usr/bin/env python
# twistedwebcalc.py -- A Twisted.Web calculator resource.

from twisted.web import server, resource
from twisted.internet import reactor

class WebCalc(resource.Resource):
    isLeaf = True

    def render_GET(self, request):
        if len(request.postpath) != 3:
            return "Usage: " + '/'.join(request.prepath) + "/op/arg1/arg2\r\n"

        op, arg1, arg2 = request.postpath
        if op not in ('add', 'sub', 'mul', 'div'):
            return "Invalid operator. Use 'add', 'sub', 'mul', or 'div'\r\n"

        result = 0.0
        try:
            numarg1 = float(arg1)
            numarg2 = float(arg2)
            if op == 'add': result = numarg1 + numarg2
            elif op == 'sub': result = numarg1 - numarg2
            elif op == 'mul': result = numarg1 * numarg2
            elif op == 'div':
                if numarg2 == 0: result = 'NaN'
                else:            result = numarg1 / numarg2
        except ValueError:
            result = "Invalid operand: use numerical values only\r\n"
        finally:
            return str(result) + "\r\n"

if __name__ == '__main__':
    root = WebCalc()
    
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedwebcalc.py

#!/usr/bin/env python
# twistedwebcalc2.py -- A Twisted.Web calculator resource.

from twisted.web import server, resource
from twisted.internet import reactor
from twistedwebcalc import WebCalc

if __name__ == '__main__':
    root = resource.Resource()
    root.putChild('calc', WebCalc())

    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedwebcalc2.py

#!/usr/bin/env python
# twistedwebcalc3.py -- A Twisted.Web web calculator using a FORM.

from twisted.web import server, resource
from twisted.internet import reactor

class WebCalcForm(resource.Resource):
    isLeaf = True

    def render_GET(self, request):
        # 1. Fetch arguments from URL or POST arguments using request
        # (which is also a twisted.web.http.Request object)
        op = request.args.get('op', None)
        arg1 = request.args.get('arg1', None)
        arg2 = request.args.get('arg2', None)

        # 2. If arguments ar missing, send input form
        if op is None or arg1 is None or arg2 is None:
            return self.create_form()

        # URL or POST arguments are always returned as a list
        # in case they occur mutiple times as in op=add&op=sub.
        op = op[0]
        arg1 = arg1[0]
        arg2 = arg2[0]

        # 3. Sanity checks and calculations
        if op not in ('add', 'sub', 'mul', 'div'):
            return "Invalid operator. Use 'add', 'sub', 'mul', or 'div'\r\n"

        result = 0.0
        try:
            numarg1 = float(arg1)
            numarg2 = float(arg2)
            if op == 'add': result = numarg1 + numarg2
            elif op == 'sub': result = numarg1 - numarg2
            elif op == 'mul': result = numarg1 * numarg2
            elif op == 'div':
                if numarg2 == 0: result = 'NaN'
                else:            result = numarg1 / numarg2
        except ValueError:
            result = "Invalid operand: use numerical values only\r\n"
        finally:
            return str(result) + "\r\n"

    def create_form(self):
        return '''
<form action='/' method='POST'>
  <input type="text" size="20" name="arg1" value="number 1" />
  <select name="op">
    <option>add</option>
    <option>sub</option>
    <option>mul</option>
    <option>div</option>
  </select>
  <input type="text" size="20" name="arg2" value="number 2" />
  <br />
  <input type="submit" value="Calculate" />
  <input type="reset" value="Clear fields" />
</form>
'''

if __name__ == '__main__':
    root = WebCalcForm()
    root.render_POST = root.render_GET
    
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedwebcalc3.py

#!/usr/bin/env python
# twistedstaticfile.py -- A Twisted.Web static file server.

from twisted.web import server, static
from twisted.internet import reactor

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print >>sys.stderr, "Usage: %s /path/to/serve" % (sys.argv[0],)
        sys.exit(1)
    
    root = static.File(sys.argv[1])
    
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedstaticfile.py

#!/usr/bin/env python
# twistedstaticandcgi.py -- A Twisted.Web static file server with cgi support

from twisted.web import server, static, twcgi
from twisted.internet import reactor

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 3:
        print >>sys.stderr, \
              "Usage: %s /static/path/to/serve /cgi/path/to/serve" % \
              (sys.argv[0],)
        sys.exit(1)
    
    root = static.File(sys.argv[1])
    root.putChild('cgi-bin', twcgi.CGIDirectory(sys.argv[2]))
    
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedstaticandcgi.py

#!/usr/bin/env python
# twistedserverpy.py -- A Twisted.Web static file server with .rpy file support

from twisted.web import server, static, script
from twisted.internet import reactor

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print >>sys.stderr, "Usage: %s /path/to/serve" % (sys.argv[0],)
        sys.exit(1)
    
    root = static.File(sys.argv[1])
    root.ignoreExt(".rpy")
    root.processors = { '.rpy': script.ResourceScript }
    
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedserverpy.py

#!/usr/bin/env python
# webcalc.rpy -- A Twisted.Web calculator resource.

# Drop this somewhere in a file system served by twisted.web.static.File
# and call up twistedserverpy.py.

# So that twistedwebcalc can be imported, uncomment the following
# two lines, and customize /path/to/your/own/python/modules:
# import sys
# sys.path.insert(0, '/path/to/your/own/python/modules')

import twistedwebcalc

# Comment out this line when finished debugging
reload(twistedwebcalc)

resource = twistedwebcalc.WebCalc()

webcalc.rpy

#!/usr/bin/env python
# twistedserve_rpy_perl_php.py -- A Twisted.Web static file server.
#                                 with .rpy, .pl und .php support

from twisted.web import server, static, script, twcgi
from twisted.internet import reactor

class PerlScript(twcgi.FilteredScript):
    filter = '/usr/bin/perl'                 # Point to the perl parser

class PHPScript(twcgi.FilteredScript):
    filter = '/usr/local/bin/php'            # Point to the PHP server

def create_server(rootpath):
    root = static.File(rootpath)
    root.processors = { '.rpy': script.ResourceScript,
                        '.pl' : PerlScript,
                        '.php': PHPScript }
    root.ignoreExt(".rpy")
    return root

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print >>sys.stderr, "Usage: %s /path/to/serve" % (sys.argv[0],)
        sys.exit(1)
    
    root = create_server(sys.argv[1])
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedserve_rpy_perl_php.py

print "Content-Type: text/plain\r\n";
print "\r\n";

print "I am a perl script\r\n";

test.pl

<?php
   print "Content-Type: text/plain\r\n";
   print "\r\n";
   phpinfo();
?>

test.php

#!/usr/bin/env python
# twistedtarpit.py -- Send a reply after a long delay.
# From: http://twistedmatrix.com/projects/web/documentation/howto/\
#       using-twistedweb.html

from twisted.web.resource import Resource
from twisted.web import server
from twisted.python.util import println
from twisted.internet import reactor

class Tarpit(Resource):
    
    isLeaf = True
    
    def __init__(self, delay):
        Resource.__init__(self)
        self.delay = delay
    
    def render_GET(self, request):
        request.write("Hello World\r\n")
        
        d = request.notifyFinish()
        d.addCallback(lambda _: println("finished normally"))
        d.addErrback(println, "error")
        reactor.callLater(self.delay, request.finish)
        
        return server.NOT_DONE_YET

resource = Tarpit(10)

if __name__ == '__main__':
    root = Tarpit(10)
    site = server.Site(root)
    reactor.listenTCP(9090, site)
    reactor.run()

twistedtarpit.py

#!/usr/bin/env python
# twistedwebsvc.py -- Running a Twisted.Web server as a Service with twistd

from twisted.application import internet, service
from twisted.web import server, static

root = static.File("/usr/local/share/doc")

application = service.Application("web")
sc          = service.IServiceCollection(application)
site        = server.Site(root)

i = internet.TCPServer(90, site)
i.setServiceParent(sc)

twistedwebsvc.py

Integration mit anderen Webservern

Lighttpd

URLs:

lighttpd_common.conf

Lighttpd und CGI

# lighttpd_cgi.conf -- minimalistic config file for mod_cgi

server.modules       = (
                         "mod_access",
                         "mod_accesslog",
                         "mod_cgi"
                       )

cgi.assign           = ( ".pl"      => "/usr/bin/perl",
                         ".php"     => "/usr/local/bin/php",
                         ".py"      => "/usr/local/bin/python",
                        )

include "lighttpd_common.conf"

lighttpd_cgi.conf

Lighttpd und FastCGI

# lighttpd_fastcgi.conf -- minimalistic config file for mod_fastcgi

server.modules       = (
                         "mod_access",
                         "mod_accesslog",
                         "mod_fastcgi"
                       )

fastcgi.server = (
                   "/fastcgi/" =>
                   ((
                      "host"         => "192.168.254.11",
                      "port"         => 20000,
                      "check-local"  => "disable",  # forward all to fcgi srv.
		      "broken-scriptfilename" => "enable"
                   )),
                   ".fcg" =>
                   ((
                      "socket"       => "/tmp/lighttpd_test_fastcgi.sock",
                      "check-local"  => "disable"  # forward all to fcgi srv.
                   ))
                 )

include "lighttpd_common.conf"

lighttpd_fastcgi.conf

FastCGI-Server in Python mit flup

URLs:

#!/usr/bin/env python
# fastcgi_server_helloworld.py -- A FastCGI server with a WSGI-App.

from flup.server.fcgi import WSGIServer

def hello_app(environ, start_response):
    "A simple WSGI application"
    start_response("200 OK", [('Content-Type', 'text/plain')])
    return ["Hello, I am a WSGI application"]

wsgi = WSGIServer(hello_app, bindAddress="/tmp/lighttpd_test_fastcgi.sock")
wsgi.run()

fastcgi_server_helloworld.py

#!/usr/bin/env python
# fastcgi_server_debug.py -- A FastCGI server with a WSGI-App.

from cStringIO import StringIO
from pprint import pformat

from flup.server.fcgi import WSGIServer

def debug_app(environ, start_response):
    "A simple WSGI debug application"
    
    buf = StringIO()
    print >>buf, 'WSGI application received environ:\r\n'
    print >>buf, pformat(environ), '\r\n'
    
    start_response("200 OK", [('Content-Type', 'text/plain')])
    return [buf.getvalue()]

wsgi = WSGIServer(debug_app, bindAddress=('', 20000))
wsgi.run()

fastcgi_server_debug.py

#!/usr/bin/env python
# fastcgi_server_webcalc.py -- A FastCGI server with a WSGI web calculator

from flup.server.fcgi import WSGIServer
from wsgicalc import webcalc

wsgi = WSGIServer(webcalc, bindAddress=('', 20000))
wsgi.run()

fastcgi_server_webcalc.py

Lighttpd und SCGI

# lighttpd_scgi.conf -- minimalistic config file for mod_scgi

server.modules       = (
                         "mod_access",
                         "mod_accesslog",
                         "mod_scgi"
                       )

scgi.server = (
                   "/scgi/" =>
                   ((
                      "host"         => "192.168.254.11",
                      "port"         => 21000,
                      "check-local"  => "disable",  # forward all to fcgi srv.
		      "broken-scriptfilename" => "enable"
                   )),
                   ".fcg" =>
                   ((
                      "socket"       => "/tmp/lighttpd_test_scgi.sock",
                      "check-local"  => "disable"  # forward all to fcgi srv.
                   ))
                 )

include "lighttpd_common.conf"

lighttpd_scgi.conf

SCGI-Server in Python mit flup

#!/usr/bin/env python
# scgi_server_webcalc.py -- A SCGI server with a WSGI web calculator

from flup.server.scgi import WSGIServer
from wsgicalc import webcalc

wsgi = WSGIServer(webcalc, bindAddress=('', 21000))
wsgi.run()

scgi_server_webcalc.py

Python in Lighttpd integrieren

Apache

URLs:

Apache und CGI

Apache und FastCGI

URLs:

Apache und SCGI

URLs:

Apache und mod_python

URLs:

Wie das Einbetten eines Python-Interpreters in C-Code funktioniert:

#include <Python.h>

int
main(int argc, char *argv[])
{
  Py_Initialize();
  PyRun_SimpleString("from time import time, ctime\n"
                     "print 'Today is', ctime(time())\n");
  Py_Finalize();
  return 0;
}

Ein Ausschnitt aus der httpd.conf:

LoadModule python_module libexec/apache22/mod_python.so

<IfModule mod_python.c>
  <Location /python>
    AddHandler mod_python .py
    PythonHandler mod_python.publisher
    PythonDebug On
  </Location>
</IfModule>
#!/usr/bin/env python
# modpycalc.py -- A webcalc handler for mod_python.

def add(req, arg1=None, arg2=None):
    req.content_type = 'text/plain'
    if arg1 is None or arg2 is None:
        return "Wrong syntax. Use /add/arg1/arg2"
    try:
        numarg1 = float(arg1)
        numarg2 = float(arg2)
    except ValueError:
        return "Non numerical operands"
    result = numarg1 + numarg2
    return str(result)

modpycalc.py

Apache und WSGI

URLs:

Ein Ausschnitt aus der httpd.conf:

LoadModule wsgi_module libexec/apache22/mod_wsgi.so

LogLevel info

<IfModule mod_wsgi.c>
    # 1. /wsgi should contain WSGI-apps.
    #    Note: the function name MUST be 'application'
    WSGIScriptAlias /wsgi/ /usr/local/www/apache22/wsgi-bin/

    # 2. Additional paths to look for python modules (:-separated)
    WSGIPythonPath "/usr/local/www/apache22/wsgi-bin"

    # 3. All applications within this directory will share the
    #    same python subinterpreter.
    <Directory /usr/local/www/apache22/wsgi-bin>
      WSGIApplicationGroup my-wsgi-scripts
      Order allow,deny
      Allow from all
    </Directory>
</IfModule>
def application(environ, start_response):
    output = 'Hello mod_wsgi\r\n'
    start_response('200 OK', [('Content-type', 'text/plain'),
                              ('Content-Length', str(len(output)))])
    return [output]

hello.wsgi

#!/usr/bin/env python
# wsgidebug.py -- A WSGI-App to print the environ.

from cStringIO import StringIO
from pprint import pformat

def debug_app(environ, start_response):
    "A simple WSGI debug application"
    
    buf = StringIO()
    print >>buf, 'WSGI application received environ:\r\n'
    print >>buf, pformat(environ), '\r\n'
    
    start_response("200 OK", [('Content-Type', 'text/plain')])
    return [buf.getvalue()]

wsgidebug.py

from wsgidebug import debug_app as application

debug.wsgi

from wsgicalc import webcalc as application

calc.wsgi

WSGI

Was ist WSGI?

def application(environ, start_response):
    output = 'Hello mod_wsgi\r\n'
    start_response('200 OK', [('Content-type', 'text/plain'),
                              ('Content-Length', str(len(output)))])
    return [output]

hello.wsgi

# wsgidummyapp.py -- an object oriented WSGI-like application class

class WSGIDummyApp(object):
    def __call__(self, environ, start_response):
        "Called by WSGI-enabled server"

        output = []

        # The following print statements are not WSGI-conform!
        # Don't write to sys.stdout from within a WSGI application!
        print 'C: I have been called with arguments:'
        print 'C:    environ: %r' % (environ,)
        print 'C:    start_response: %r' % (start_response,)

        if 'CLIENT' in environ:
            output.append("Hello Client %r\n" % (environ['CLIENT'],))
        else:
            output.append("Hello WSGI World\n")
        output.append("Nice to meet you\n")
        output_length = str(len(''.join(output)))

        print 'C: I will call start_response now'
        start_response('200 OK', [('Content-type', 'text/plain'),
                                  ('Content-Length', str(len(output)))])

        print 'C: Finished calling start_response'
        print 'C: now, returning output iterable'
        return output

wsgidummyapp.py

#!/usr/bin/env python
# wsgidummyserver.py -- a pseudo-server that calls a WSGI application.

class WSGIDummyServer(object):
    "A dummy server that calls a WSGI application"

    def __init__(self, app):
        self.app = app
        self.app.iterable = None
        self.client = None

    def handle_request(self, client):
        "Handle a request by a client. client is a file-like object"

        print "S: handle_request() called"
        
        self.client = client

        # The following environment is NOT WSGI-conform!
        # It's missing CGI- and wsgi.* values required by the spec.
        environment = { 'CLIENT': client }

        print 'S: About to call application with arguments:'
        print 'S:   environ:', environment
        print 'S:   start_response:', self.my_start_response
        self.app.iterable = self.app(environment, self.my_start_response)

        print 'S: Finished calling application. Got iterable back.'

        for chunk in self.app.iterable:
            print "S: got a chunk from app:"
            print "S:   chunk was:", chunk
            print "S: sending chunk to client"
            self.client.write(chunk)

        print 'S: Got all chunks from app iterator. Closing client conn.'
        
        # A real webserver would close the connection to the client here.
        # self.client.close()
        self.client = None
        self.app.iterable = None

    def my_start_response(self, status, response_headers):
        "Send initial headers back to client"

        print "S: my_start_response() called"

        self.client.write(status + "\r\n")
        for key, value in response_headers:
            self.client.write("%s=%s\r\n" % (key, value))
        self.client.write("\r\n")

wsgidummyserver.py

#!/usr/bin/env python
# wsgidummydemo.py -- show interaction between WSGIDummyServer and MyApp

from wsgidummyapp import WSGIDummyApp
from wsgidummyserver import WSGIDummyServer
from cStringIO import StringIO

app    = WSGIDummyApp()
server = WSGIDummyServer(app)
client = StringIO()

server.handle_request(client)

print '-' * 70
print 'Client got the following reply:'
print client.getvalue()

wsgidummydemo.py

URLs:

WSGI-Tools

Die wsgiref.*-Module

WSGI-Utils

URLs:

flup

Wurde weiter oben schon eingeführt.

Python Paste

URLs:

Low-level-Programmierung mit CGI

Hello, CGI World!

#!/usr/bin/env python
# cgihello.py -- Hello, CGI World.

from sys import stdout

stdout.write("Content-type: text/plain\r\n")
stdout.write("\r\n")
stdout.write("Hello, CGI World!\r\n")

cgihello.py

CGI-Umgebungsvariablen

#!/usr/bin/env python
# cgiprintenv.py -- Display CGI Environment Variables.

import os
from sys import stdout

stdout.write("Content-type: text/plain\r\n\r\n")

for key in sorted(os.environ.keys()):
    print "%s=%s" % (key, os.environ[key])

cgiprintenv.py

Screenshots:

Anwendung: Ein Web-Taschenrechner

#!/usr/bin/env python
# cgicalc.py -- A web calculator using PATH_INFO

from __future__ import division
import os, sys

class WebCalculator(object):
    def add(self,a,b): return a+b
    def sub(self,a,b): return a-b
    def mul(self,a,b): return a*b
    def div(self,a,b):
        try:
            return a/b
        except ZeroDivisionError:
            return "ZeroDivisionError"

def main():
    sys.stdout.write("Content-type: text/plain\r\n\r\n")
    if 'PATH_INFO' not in os.environ:
        sys.stdout.write("Usage: cgicalc.py/{add,sub,mul,div}/a/b\r\n")
        sys.exit()

    paramlist = os.environ['PATH_INFO'].split('/')
    if len(paramlist) != 4:
        sys.stdout.write("Wrong number of parameters\r\n")
        sys.exit()

    func, arg1, arg2 = paramlist[1:]
    calc = WebCalculator()
    f = getattr(calc, func, None)
    if f is None:
        sys.stdout.write("Function %s is not defined\r\n" % (func,))
        sys.exit()

    try:
        a1 = float(arg1)
        a2 = float(arg2)
    except ValueError:
        sys.stdout.write("Arguments must be float\r\n")
        sys.exit()
            
    result = str(f(a1,a2))
    sys.stdout.write("%s(%s,%s) = %s\r\n" % (func, arg1, arg2, result))

if __name__ == '__main__':
    main()

cgicalc.py

Ein Formular manuell auslesen

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>A simple data entry form</title>
  </head>
  <body>
    <h1>A simple data entry form</h1>
    <form action="cgiform.py" method="GET">
      <input type="text" size="40" name="username" />    (User name)<br />
      <input type="text" size="40" name="email" />       (User Email)<br />
      <input type="password" size="8" name="password" /> (User Password)<br />
      <input type="text" size="40" name="prog" />        (Prog. Lang. #1)<br />
      <input type="text" size="40" name="prog" />        (Prog. Lang. #2)<br />
      <input type="text" size="40" name="prog" />        (Prog. Lang. #3)<br />
      <input type="submit" value="Send data"/>
    </form>
  </body>
</html>

cgiformget.html

#!/usr/bin/env python
# cgiform.py -- decode form data from CGI

import os, sys, urllib

reqmeth = os.environ['REQUEST_METHOD']

if reqmeth == 'GET':
    data = os.environ['QUERY_STRING']
elif reqmeth == 'POST':
    data = sys.stdin.read()
else:
    data = "Unknown method %s. Must be GET or POST" % (reqmeth)

sys.stdout.write('Content-type: text/plain\r\n\r\n')
sys.stdout.write('Got the following data with %s method:\r\n' % (reqmeth,))
sys.stdout.write(data + '\r\n\r\n')

sys.stdout.write('Partially decoding fields:\r\n')
fields = data.split('&')
for field in fields:
    fieldname, fieldvalue = field.split('=')
    sys.stdout.write(' ' + fieldname + ': ' + fieldvalue + '\r\n')

cgiform.py

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>A simple data entry form</title>
  </head>
  <body>
    <h1>A simple data entry form</h1>
    <form action="cgiform.py" method="POST">
      <input type="text" size="40" name="username" />    (User name)<br />
      <input type="text" size="40" name="email" />       (User Email)<br />
      <input type="password" size="8" name="password" /> (User Password)<br />
      <input type="text" size="40" name="prog" />        (Prog. Lang. #1)<br />
      <input type="text" size="40" name="prog" />        (Prog. Lang. #2)<br />
      <input type="text" size="40" name="prog" />        (Prog. Lang. #3)<br />
      <input type="submit" value="Send data"/>
    </form>
  </body>
</html>

cgiformpost.html

Das cgi-Modul

#!/usr/bin/env python
# cgiform2.py -- decode form data from CGI using the cgi module

import cgitb; cgitb.enable()
import cgi

print "Content-type: text/html"
print

result = []
result.append("<html><head><title>Results</title></head><body>")
result.append("<ul>")

form = cgi.FieldStorage()
for field in form.keys():
    valuelist = form.getlist(field)
    for value in valuelist:
        result.append("<li>%s: %s</li>" %
                      (cgi.escape(field), cgi.escape(value)))

result.append("</ul></body></html>")
print '\n'.join(result)

cgiform2.py

#!/usr/bin/env python
# cgierror.py -- show stack track with cgitb

import cgitb; cgitb.enable()

print "Content-type: text/html"
print

class MyError(Exception): pass

def foo(inpt): bar(inpt+1)
def bar(data): baz(data+1)
def baz(param): raise MyError("Something's wrong in baz")

foo(42)

cgierror.py

Screenshots:

Anwendung: Dateien uploaden

#!/usr/bin/env python
# cgiupload.py -- CGI to upload a file to a directory on the webserver.

import cgitb; cgitb.enable()
import cgi
import sys, os.path

DESTDIR   = '/tmp'           # CHANGE ME!
PASSWORD  = 'smurf32ii'      # CHANGE ME!
BLOCKSIZE = 4096

FORMTEXT  = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>A simple file upload form</title>
  </head>
  <body>
    <h1>Upload a file to %(destdir)s</h1>
    <form action="%(action)s" method="POST"
          enctype="multipart/form-data">
      <input type="file" name="filename" />(File Name)<br />
      <input type="password" size="8" name="password" /> (User Password)<br />
      <input type="submit" value="Upload file"/>
    </form>
  </body>
</html>''' % { 'destdir': DESTDIR,
               'action': os.environ.get('SCRIPT_NAME', 'not-a-cgi') }

def bailout(result):
    result.append('</body></html>')
    print '\n'.join(result)
    sys.exit(0)

print "Content-type: text/html"
print

result = []    # HTML reply
result.append('<html><head><title>Upload result</title></head><body>')

form = cgi.FieldStorage()
if 'filename' not in form:
    # User didn't send anything yet.
    print FORMTEXT
    sys.exit(0)
    
try:
    filename = form['filename'].filename
    password = form['password'].value
except:
    result.append('<p>Filename or password missing. Nothing uploaded.</p>')
    result.append('</body></html>')
    bailout(result)

if password != PASSWORD:
    result.append('<p>Wrong password. Nothing uploaded.</p>')
    result.append('</body></html>')
    bailout(result)

# Sanitize (somewhat) output filename
outfilename = filename.replace(os.path.sep, '_').replace(os.path.pardir, '_')
outfilename = os.path.join(DESTDIR, outfilename)

# Now copy chunkwise from form['filename'].file to outfilename
nbytes = 0L
f_inp = form['filename'].file
f_out = open(outfilename, 'wb')
while (True):
    chunk = f_inp.read(BLOCKSIZE)
    if not chunk: break
    f_out.write(chunk)
    nbytes += len(chunk)
f_out.close()
f_inp.close()

# That's all, folks!
result.append('<p>Uploaded %d bytes from %s to %s</p>' %
              (nbytes, filename, outfilename))
bailout(result)

cgiupload.py

Das Formular, welches das Eingabefenster erzeugt:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>A simple file upload form</title>
  </head>
  <body>
    <h1>Upload a file to /tmp</h1>
    <form action="/cgiupload.py" method="POST"
          enctype="multipart/form-data">
      <input type="file" name="filename" />(File Name)<br />
      <input type="password" size="8" name="password" /> (User Password)<br />
      <input type="submit" value="Upload file"/>
    </form>
  </body>
</html>

Den Zustand clientseitig erhalten

Zustand als Teil der URL

#!/usr/bin/env python
# cgistate_pathinfo.py -- Preserving CGI state with PATH_INFO

import cgitb; cgitb.enable()
import os, os.path

def state_fetch():
    try:
        state = int(os.environ.get('PATH_INFO', '/1')[1:])
    except ValueError:
        state = 1
    finally:
        return state

def state_update(state):
    state = state + 1
    return state

def state_store(state):
    if 'SCRIPT_NAME' not in os.environ:
        return "This is not a CGI script. Can't call again."
    else:
        return 'Call me <a href="%s/%d">again</a><br />' % \
               (os.environ['SCRIPT_NAME'], state)

print "Content-type: text/html"
print

result = []
result.append('<html><head><title>Counter 1</title></head></body><p>')

# Fetch old counter value from client
oldstate = state_fetch()
result.append('Old Counter: %d<br />' % (oldstate,))

# Compute new counter value
newstate = state_update(oldstate)
result.append('New Counter: %d<br />' % (newstate,))

# Send new counter value to client
result.append(state_store(newstate))

result.append('</p></body></html>')
print '\n'.join(result)

cgistate_pathinfo.py

hidden-Felder

Ein Formular mit einem Hidden-Feld:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>CGI Counter 2</title>
  </head>
  <body>
    <p>Current counter value: 4</p>
    <form action="/cgi-bin/cgistate_hiddenfields.py" method="POST">
      <input type="hidden" name="state" value="4" />
      <input type="submit" value="Increment counter"/>
    </form>
  </body>
</html>
#!/usr/bin/env python
# cgistate_hiddenfields.py -- Preserving CGI state with HTML hidden fields

import cgitb; cgitb.enable()
import cgi
import sys, os

FORMTEXT_TMPL  = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>CGI Counter 2</title>
  </head>
  <body>
    <p>Current counter value: %(value)s</p>
    <form action="%(action)s" method="POST">
      <input type="hidden" name="state" value="%(value)s" />
      <input type="submit" value="Increment counter"/>
    </form>
  </body>
</html>'''

def state_fetch(form):
    try:
        state = int(form['state'].value)
    except:
        state = 1
    finally:
        return state

def state_update(state):
    state = state + 1
    return state

def state_store(state):
    if 'SCRIPT_NAME' not in os.environ:
        return "This is not a CGI script. Can't call again."
    else:
        return FORMTEXT_TMPL % \
               { 'action': os.environ.get('SCRIPT_NAME', 'not_a_cgi'),
                 'value' : str(state) }

print "Content-type: text/html"
print

result = []

form = cgi.FieldStorage()
if 'state'  not in form:
    print FORMTEXT_TMPL % \
          { 'action': os.environ.get('SCRIPT_NAME', 'not_a_cgi' ),
            'value':  '1' }
    sys.exit(0)

# Fetch old counter value from client
oldstate = state_fetch(form)

# Compute new counter value
newstate = state_update(oldstate)

# Send new counter value to client
result.append(state_store(newstate))
print '\n'.join(result)

cgistate_hiddenfields.py

Cookies

#!/usr/bin/env python
# cgistate_cookies.py -- Preserving CGI state with cookies

import cgitb; cgitb.enable(logdir='/tmp/cgitb', format='text')
import Cookie
import os

def state_fetch():
    try:
        c = Cookie.SimpleCookie()
        c.load(os.environ['HTTP_COOKIE'])
        state = int(c['state'].value)
    except KeyError:
        state = 1
    except ValueError:
        state = 1
    finally:
        return state

def state_update(state):
    state = state + 1
    return state

def state_store(state):
    c = Cookie.SimpleCookie()
    c['state'] = state
    c['state']['expires'] = 3600  # forget state in 3600 seconds = 1 hour
    c['state']['path'] = '/'      # cookie valid for the whole domain
    return c.output()

result_http = []
result_http.append("Content-type: text/html")

result_html = []
result_html.append('<html><head><title>Counter 3</title></head></body><p>')

# Fetch old counter value from client
oldstate = state_fetch()
result_html.append('Old Counter: %d<br />' % (oldstate,))

# Compute new counter value
newstate = state_update(oldstate)
result_html.append('New Counter: %d<br />' % (newstate,))

# Send new counter value to client
result_http.append(state_store(newstate))

# Send feedback
result_html.append('Call me <a href="%s">again</a><br />' %
                   os.environ.get('SCRIPT_NAME', 'not_a_cgi'))

result_html.append('</p></body></html>')

print '\n'.join(result_http)
print
print '\n'.join(result_html)

cgistate_cookies.py

Screenshots:

Den Zustand fälschungssicherer machen

Screenshots:

URLs:

#!/usr/bin/env python
# cgistate_cookies.py -- Preserving CGI state with cookies

import cgitb; cgitb.enable(logdir='/tmp/cgitb', format='text')
import Cookie
import os

def state_fetch():
    try:
        c = Cookie.SimpleCookie()
        c.load(os.environ['HTTP_COOKIE'])
        state = int(c['state'].value)
    except KeyError:
        state = 1
    except ValueError:
        state = 1
    finally:
        return state

def state_update(state):
    state = state + 1
    return state

def state_store(state):
    c = Cookie.SimpleCookie()
    c['state'] = state
    c['state']['expires'] = 3600  # forget state in 3600 seconds = 1 hour
    c['state']['path'] = '/'      # cookie valid for the whole domain
    return c.output()

result_http = []
result_http.append("Content-type: text/html")

result_html = []
result_html.append('<html><head><title>Counter 3</title></head></body><p>')

# Fetch old counter value from client
oldstate = state_fetch()
result_html.append('Old Counter: %d<br />' % (oldstate,))

# Compute new counter value
newstate = state_update(oldstate)
result_html.append('New Counter: %d<br />' % (newstate,))

# Send new counter value to client
result_http.append(state_store(newstate))

# Send feedback
result_html.append('Call me <a href="%s">again</a><br />' %
                   os.environ.get('SCRIPT_NAME', 'not_a_cgi'))

result_html.append('</p></body></html>')

print '\n'.join(result_http)
print
print '\n'.join(result_html)

cgistate_cookies.py

#!/usr/bin/env python
# cryptutils.py -- crypto utility functions

import Crypto.Cipher.AES
import hmac, hashlib

def HMAC(data, key):
    """HMAC
    Compute the HMAC algorithm of RFC 2104 on DATA using the key KEY
    and the digest algorithm SHA-256.
    Return the compute HMAC as string.
    """
    hmacer = hmac.HMAC(key, digestmod=hashlib.sha256)
    hmacer.update(data)
    return hmacer.hexdigest()

def Encrypt(data, key):
    """Encrypt
    Encrypt DATA with KEY using the AES algorithm in ECB mode.
    KEY must be 16, 24, or 32 bytes long. DATA must be a multiple
    of 16 in length.
    Return a the encrypted text.
    """
    encrypter = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_ECB)
    return encrypter.encrypt(data)

def Decrypt(data, key):
    """Decrypt
    Decrypt DATA with KEY using the AES algorithm in ECB mode.
    KEY must be 16, 24, or 32 bytes long. DATA must be a multiple
    of 16 in length.
    Return the decrypted data as string.
    """
    decrypter = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_ECB)
    return decrypter.decrypt(data)

def pad(data, padlength=16, padbyte='\x00'):
    padsize = padlength - (len(data) % padlength)
    padded = data + padbyte * padsize
    return padded, padsize

def unpad(data, padsize=0):
    return data[:-padsize]

cryptutils.py

#!/usr/bin/env python
# cryptutils_demo.py -- demo of cryptutils functions

from cryptutils import HMAC, Encrypt, Decrypt, pad, unpad
from base64 import b64encode, b64decode

senddata, key = "hello", "0123456789abcdef"
padded_data, pad_size = pad(senddata, 16, 'X')

# Encrypt padded_data:
cyphertext = Encrypt(padded_data, key)
signature  = HMAC(padded_data, key)            # 64 bytes!
sendmsg    = b64encode(cyphertext + signature)

# Decrypt message:
recvmsg    = b64decode(sendmsg)
cyphertext = recvmsg[:-64]
signature  = recvmsg[len(recvmsg)-64:]
plaintext  = Decrypt(cyphertext, key)
if HMAC(plaintext, key) != signature:
    raise Exception("HMAC mismatch!")

recvdata = unpad(plaintext, pad_size)

if recvdata != senddata:
    raise Exception("Decrypt o Encrypt != id")
print "Received: [%s]" % (recvdata,)

cryptutils_demo.py

#!/usr/bin/env python
# cryptcookie.py -- An encrypted and signed Cookie class

from base64 import b64encode, b64decode
import Cookie
import cryptutils

class ModifiedCookieError(Exception): pass

class CryptCookie(Cookie.BaseCookie):
    "A collection of cookies that are encrypted and signed"
    
    def __init__(self, serverkey):
        super(CryptCookie, self).__init__()
        self.serverkey = serverkey

    def value_encode(self, val):
        data, pad_size = cryptutils.pad(str(val), 16, ' ')
        cyphertext     = cryptutils.Encrypt(data, self.serverkey)
        signature      = cryptutils.HMAC(data, self.serverkey)     # 64 bytes!
        message        = b64encode(cyphertext + signature)
        return val, message

    def value_decode(self, val):
        recvmsg        = b64decode(val)
        cyphertext     = recvmsg[:-64]
        signature      = recvmsg[len(recvmsg)-64:]
        plaintext      = cryptutils.Decrypt(cyphertext, self.serverkey)
        if cryptutils.HMAC(plaintext, self.serverkey) != signature:
            raise ModifiedCookieError()
        return plaintext, val    # plaintext is probably still padded!

cryptcookie.py

#!/usr/bin/env python
# cgistate_cryptcookies.py -- Preserving CGI state with encrypted cookies

import sys, os
sys.path.append(os.environ['SCRIPT_FILENAME'])

import cgitb; cgitb.enable(logdir='/tmp/cgitb', format='text')
from cryptcookie import CryptCookie, ModifiedCookieError

SERVER_KEY = 'oj2kj6/vboU872lj'    # must be 16 bytes

def state_fetch():
    try:
        c = CryptCookie(SERVER_KEY)
        c.load(os.environ['HTTP_COOKIE'])
        state = int(c['state'].value)
    except KeyError:
        state = 1                  # No cookie yet, start with 1
    except ValueError:
        state = -1000              # There was no integer in the cookie
    except ModifiedCookieError:
        state = -2000              # Someone tampered with the cookie
    except:
        state = -3000              # Some other error occured
    finally:
        return state

def state_update(state):
    state = state + 1
    return state

def state_store(state):
    c = CryptCookie(SERVER_KEY)
    c['state'] = state
    c['state']['expires'] = 3600  # forget state in 3600 seconds = 1 hour
    c['state']['path'] = '/'      # cookie valid for the whole domain
    return c.output()

result_http = []
result_http.append("Content-type: text/html")

result_html = []
result_html.append('<html><head><title>Counter 4</title></head></body><p>')

# Fetch old counter value from client
oldstate = state_fetch()
result_html.append('Old Counter: %d<br />' % (oldstate,))

# Compute new counter value
newstate = state_update(oldstate)
result_html.append('New Counter: %d<br />' % (newstate,))

# Send new counter value to client
result_http.append(state_store(newstate))

# Send feedback
result_html.append('Call me <a href="%s">again</a><br />' %
                   os.environ.get('SCRIPT_NAME', 'not_a_cgi'))

result_html.append('</p></body></html>')

print '\n'.join(result_http)
print
print '\n'.join(result_html)

cgistate_cryptcookies.py

#!/usr/bin/env python
# securecookieprotocol.py -- A Secure Cookie Protocol by Alex X. Liu et al.
# http://www.cse.msu.edu/~alexliu/publications/Cookie/cookie.pdf

'''
Pseudo code of the the Secure Cookie Protocol proposed in the paper

   A Secure Cookie Protocol
   Alex X. Liu, Jason M. Kovacs, Chin-Tser Huang, and Mohamed G. Gouda
   http://www.cse.msu.edu/~alexliu/publications/Cookie/cookie.pdf
'''

from cryptutils import Encrypt, Decrypt, HMAC
import time

def Encrypt_SCP(username, expiration_time, data, session_key, server_key):
    '''Encrypt and sign data using "A Secure Cookie Protocol."
    
    Create and return an encrypted and signed cookie payload for a
    user using a secure cookie protocol.
    
    USERNAME        is the name of the cookie owner.
    SESSION_KEY     is the SSL session ID of the Client/Server connection.
    EXPIRATION_TIME is the time in seconds since the Epoch when the
                    cookie is set to expire... as String.
    DATA            is the data to be encrypted
    SERVER_KEY      is a secret key private to the server.
    
    SCP creates a payload containing:
      USERNAME, EXPIRATION_TIME in plaintext,
      The encrypted DATA, using a key that is difficult to guess,
      a hard to forge signature.
    
    SCP uses the Encrypt and HMAC functions from cryptutils.py
    '''
    
    k = HMAC(username + expiration_time, server_key)
    C_enc = username + expiration_time + Encrypt(data, k) + \
            HMAC(username + expiration_time + data + session_key, k)
    
    return C_enc

def Decrypt_SCP(payload, session_key, server_key):
    '''Verify and decrypt payload according to "A Secure Cookie Protocol."
    
    Decrypt the cookie payload PAYLOAD and return a tuple
    (verified, decrypted_data). VERIFIED is True only if the
    payload has not been tampered with, and DECRYPTED_DATA
    contains the decrypted payload, but only if VERIFIED is True.
    
    PAYLOAD is the encrypted payload as returned by Encrypt_SCP
    SESSION_KEY is the SSL session ID of the Client/Server connection.
    SERVER_KEY  is a secret key private to the SERVER.
    
    Decrypt and HMAC are from cryptutils.py
    '''
    
    if int(payload.expiration_time) < time.time():
        return (False, None)        # Cookie expired.
    
    k         = HMAC(payload.username + payload.expiration_time, server_key)
    data      = Decrypt(payload.encrypted_data, k)
    signature =  HMAC(payload.username + payload.expiration_time + data + \
                      session_key, k)
    if computed_signature != payload.signature:
        return (False, None)        # Cookie has been tampered with
    
    return (True, data)             # Cookie is valid.

securecookieprotocol.py

Den Zustand serverseitig erhalten

Sitzungen

Die grundlegende Vorgehenseweise bei Sitzungen (Pseudocode):

def handle_request(req):

    session = DB_Session(session_time=300)  # 5 minutes = 300 secs. sessions
    cookie  = req.getCookie('session')      # decrypt and verify, else None

    if cookie:
        sess_id = cookie.getSessionID()
        if session.session_valid(sess_id):
            if req.getCommand() == LOGOUT:
                session.session_remove(sess_id)
                send_cookie(create_cookie(LOGGED_OUT))
                confirmLogoutToUser()
                send_login_form()
            else:
                do_something_with_session_and_request(sess_id, req)
                session.session_extend_time(sess_id)
                send_some_reply()
        else:
            sendErrorToUser(INVALID_OR_EXPIRED_SESSION)
            send_login_form()

    else:
        if req.hasLoginCredentials():
            username, password = req.loginCredentials()
            if verify_credentials(username, password):
                sess_id = session.session_create(username)
                send_cookie(create_cookie(sess_id)) # encrypt and sign cookie
                confirmLoginToUser()
                show_logged_in_page()
            else:
                sendErrorToUser(INVALID_USER)
                send_login_form()
        else:
            send_login_form()

    session.close()
#!/usr/bin/env python
# session.py -- abstract class representing a server-side session

class BaseSession(object):
    "Abstract class represending a server-side session"
    def __init__(self, session_time=3600):
        "Initialize database connections etc. here. Don't forget to call me"
        self.expires_interval = session_time

    def close(self):
        "Close database connections here"
        raise NotImplementedError('Method must be implemented by subclass')

    def session_create(self, user_id):
        "Create a new session for user user_id. Return new session_id"
        raise NotImplementedError('Method must be implemented by subclass')

    def session_remove(self, session_id):
        "Remove the session with the ID session_id"
        raise NotImplementedError('Method must be implemented by subclass')

    def session_valid(self, session_id):
        "Verify that the session is still valid by checking its expire time"
        raise NotImplementedError('Method must be implemented by subclass')

    def session_extend_time(self, session_id):
        "Extend the expire time by self.expires_interval"
        raise NotImplementedError('Method must be implemented by subclass')

session.py

Datenbankbasierte Sitzungen

#!/usr/bin/env python
# session_dbapi.py -- server-side sessions using DB-API 2.0

'''
session_dbapi.py implements a session.BaseSession on PostgreSQL.
It assumes a table "session" that has been defined as follows:

CREATE TABLE sessions (
    session_id BIGSERIAL PRIMARY KEY,
    user_id    VARCHAR(8),
    expires    INTEGER
);
'''

import time
import psycopg2
import session

def now():
    return int(time.time())

class DBSession(session.BaseSession):
    "A server-side session saved in a DB-API 2.0 table"
    def __init__(self, dsn, session_time=3600):
        super(DBSession, self).__init__(session_time)
        self.dsn = dsn
        self.conn = psycopg2.connect(dsn)
        self.curs = self.conn.cursor()

    def close(self):
        self.curs.close()
        self.conn.close()
        self.conn = self.curs = None
        
    def session_create(self, user_id):
        "Create a new session for user user_id. Return new session_id"
        data = { 'user_id': user_id,
                 'expires': now() + self.expires_interval }
        self.curs.execute("INSERT INTO sessions ( user_id, expires ) "
                          "VALUES ( '%(user_id)s', %(expires)d )" %
                          data)
        self.curs.execute("SELECT currval('sessions_session_id_seq')")
        session_id = self.curs.fetchone()[0]
        self.conn.commit()
        return session_id

    def session_remove(self, session_id):
        "Remove the session with the ID session_id"
        data = { 'session_id': session_id }
        self.curs.execute("DELETE FROM sessions "
                          "WHERE session_id = %(session_id)s" %
                          data)
        self.conn.commit()

    def session_valid(self, session_id):
        "Verify that the session is still valid by checking its expire time"
        data = { 'session_id': session_id }
        self.curs.execute("SELECT expires FROM sessions "
                          "WHERE session_id = %(session_id)d" %
                          data)
        expires = self.curs.fetchone()[0]
        return now() <= expires

    def session_extend_time(self, session_id):
        "Extend the expire time by self.expires_interval"
        data = { 'session_id': session_id,
                 'expires'   : now() + self.expires_interval }
        self.curs.execute("UPDATE sessions SET expires = %(expires)d "
                          "WHERE session_id = %(session_id)d" %
                          data)
        self.conn.commit()

session_dbapi.py

Dateibasierte Sitzungen

Nachteile von CGI

Webclients

Low-level HTTP Protokoll mit httplib

Einfache Webclients mit urllib

Flexiblere Webclients mit urllib2

Eine Seite anfordern

Eine URL herunterladen:

import urllib2
theurl = 'https://pythonbook.hajji.org/examples/net/test.html'

f = urllib2.urlopen(theurl)
data = f.read()
f.close()

Man kann auch zeilenweise iterieren:

f = urllib2.urlopen(theurl)
for line in f:
    thelist.append(line)
f.close()
#!/usr/bin/env python
# fetchchunked.py -- download a URL chunkwise, write it to sys.stdout

import sys
import urllib2

BLOCKSIZE = 8192

def fetch_chunked(url, chunksize=BLOCKSIZE):
    "Fetch a 'url' to sys.stdout, using 'chunksize' bytes."

    f = urllib2.urlopen(url)
    chunk = f.read(chunksize)
    while chunk:
        sys.stdout.write(chunk)
        chunk = f.read(chunksize)
    f.close()

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print "Usage:", sys.argv[0], "URL chunksize"
        sys.exit(1)
    
    url   = sys.argv[1]
    csize = int(sys.argv[2])
    
    fetch_chunked(url, csize)

fetchchunked.py

Einen Teil einer Seite anfordern

Alles, mit Ausnahme der 30 ersten Bytes laden:

import urllib2

opener = urllib2.build_opener()
opener.addheaders = [('Range', 'bytes=30-'), ('User-agent', 'mybot/0.0')]
op = opener.open('https://pythonbook.hajji.org/examples/net/test.html')
data = op.read()

Webclients mit Twisted

#!/usr/bin/env python
# twisted_fetcher.py -- fetch and display a page via HTTP using Deferred.

from twisted.internet import reactor
from twisted.web.client import getPage
import sys

def contentHandler(thepage):
    print thepage,
    reactor.stop()

def errorHandler(theerror):
    print theerror
    reactor.stop()

d = getPage(sys.argv[1])
d.addCallback(contentHandler)
d.addErrback(errorHandler)

reactor.run()

twisted_fetcher.py

Templating Engines

URLs:

Templating für arme Leute

Stringinterpolation

From: %(from)s
To: %(to)s
Subject: %(subject)s

You are %(age)d years old.

template1.txt

From: %(from)s
To: %(to)s
Cc: %(cc)s
Subject: %(subject)s

You are %(age)d years old.

template2.txt

From: %(from)s
To: %(to)s%(cc)s
Subject: %(subject)s

You are %(age)d years old.

template3.txt

string.Template

Screenshots:

From: $from
To: $to
Subject: $subject

You are $age ${unit}s old.

template4.txt

Text-basiertes Templating

Mako

Screenshots:

URLs:

From: ${fromvar}
To: ${to}
Subject: ${subject}

${body}

mako1.txt

from mako import runtime, filters, cache
UNDEFINED = runtime.UNDEFINED
_magic_number = 2
_modified_time = 1210580860.5037041
_template_filename='mako1.txt'
_template_uri='mako1.txt'
_template_cache=cache.Cache(__name__, _modified_time)
_source_encoding=None
_exports = []


def render_body(context,**pageargs):
    context.caller_stack.push_frame()
    try:
        __M_locals = dict(pageargs=pageargs)
        body = context.get('body', UNDEFINED)
        to = context.get('to', UNDEFINED)
        fromvar = context.get('fromvar', UNDEFINED)
        subject = context.get('subject', UNDEFINED)
        # SOURCE LINE 1
        context.write(u'From: ')
        context.write(unicode(fromvar))
        context.write(u'\nTo: ')
        # SOURCE LINE 2
        context.write(unicode(to))
        context.write(u'\nSubject: ')
        # SOURCE LINE 3
        context.write(unicode(subject))
        context.write(u'\n\n')
        # SOURCE LINE 5
        context.write(unicode(body))
        context.write(u'\n')
        return ''
    finally:
        context.caller_stack.pop_frame()


mako1.txt.py

<html>
  <head>
    <title>${title}</title>
  </head>
  <body>
    <h1>${title}</h1>

mako_header.txt

  </body>
</html>

mako_footer.txt

    <p>${a} + ${b} = ${a + b}</p>

mako_main.txt

<%include file="mako_header.txt"/>
<%include file="mako_main.txt"/>
<%include file="mako_footer.txt"/>

mako_all.txt

#!/usr/bin/env python
# showmako.py -- render mako templates

from mako.template import Template
from mako.lookup import TemplateLookup

class Renderer(object):
    def __init__(self, dirs='.', moddir='/tmp/mako'):
        self.lookup = TemplateLookup(directories=dirs,
                                     module_directory=moddir)
    def render(self, templatename, **kwargs):
        self.tmpl = self.lookup.get_template(templatename)
        return self.tmpl.render(**kwargs)
    def render_unicode(self, templatename, **kwargs):
        self.tmpl = self.lookup.get_template(templatename)
        return self.tmpl.render_unicode()

if __name__ == '__main__':
    import sys
    r = Renderer('.', '/tmp/mako')
    print r.render(sys.argv[1]),

showmako.py

## A simple multiplication table
<table>
  % for row in range(row_begin, row_end):
  <tr>
    % for col in range(col_begin, col_end):
    <td>${row * col}</td>
    % endfor
  </tr>
  % endfor
</table>

mako2.txt

<%!
    import time
%>
From: ${fromvar}
To: ${to}
% if cc != UNDEFINED:
CC: ${cc}
% endif
Date: ${time.ctime()}
Subject: ${subject}

${body}

mako3.txt

<%!
    import time
%>
<%
    if isinstance(to, list):
        toval = ', '.join(to)
    else:
        toval = to

    if cc != UNDEFINED:
        if isinstance(cc, list):
            ccval = '\n'.join(['CC: ' + c for c in cc])
        else:
            ccval = 'CC: ' + cc
%>
From: ${fromvar}
To: ${toval}
% if cc != UNDEFINED:
${ccval}
% endif
Date: ${time.ctime()}
Subject: ${subject}

${body}

mako4.txt

% if data is not UNDEFINED and isinstance(data, list):
<table>
  % for idx, elem in enumerate(data):
  <%
      if idx % 2 == 0:
          color = "lighter"
      else:
          color = "darker"
  %> <tr><td class="${color}">${elem}</td></tr>
  % endfor
</table>
% elif data is not UNDEFINED:
<p>${data}</p>
% else:
<p>No data available</p>
% endif

mako5.txt

<%!
    def foo():
        return "I am foo"
%>
<%
    foosaid = foo()
%>
Foo said: ${foo()}.
Foo said: ${foosaid}.

mako6.txt

<%!
    import string

    def tagit(text):
        return "<tag>%s</tag>" % (text,)
%>

Simple Python Expression          : ${a} + ${b} = ${a + b}
Python Expression with a builtin  : pow(${a}, ${b}) = ${pow(a, b)}
Python Expression with tagit      : ${tagit('test')}

Filtering with u                  : ${"this is (was) a test" | u}
Filtering with h                  : ${"<Smith & Wesson>"     | h}
Filtering with trim               : ${"   this is a test   " | trim}

Filtering with u,trim             : ${"   this is a test   " | u,trim}
Filtering with trim,u             : ${"   this is a test   " | trim,u}

Filterint with string.upper       : ${"this is a test" | string.upper}
Filtering with tagit              : ${"tagged text" | tagit}

mako7.txt

Calling adder(3, 4): ${adder(3, 4)}

<%def name="adder(a, b)">
    The sum of ${a} and ${b} is ${a + b}
</%def>

mako8.txt

<%def name="add(a, b)">add(${a}, ${b}) is ${a + b}</%def>
<%def name="sub(a, b)">sub(${a}, ${b}) is ${a - b}</%def>
<%def name="mul(a, b)">mul(${a}, ${b}) is ${a * b}</%def>
<%def name="div(a, b)">div(${a}, ${b}) is ${a / b}</%def>

<%def name="summer(lst)">summer(${lst}) is ${sum_aux(lst)}</%def>
<%def name="sum_aux(lst)">\
  <%
    result = 0
    for elem in lst:
        result = result + elem
  %>\
  ${result}\
</%def>

<%def name="multiplier(lst)">\
  <%def name="mul_aux(lst)">\
    <%
       result = 1
       for elem in lst:
           result = result * elem
    %>${result}\
  </%def>multiplier(${lst}) is ${mul_aux(lst)}\
</%def>

makocalc.txt

<%namespace name="calc" file="makocalc.txt"/>

${calc.add(3,4)}
${calc.sub(3,4)}
${calc.mul(3,4)}
${calc.div(3,4)}

${calc.summer([10, 20, 30, 40, 50])}
${calc.multiplier([5, 10, 15, 20, 25])}

mako9.txt

<%def name="foo()">
  <footag>
    ${caller.body()}
  </footag>
</%def>

<%call expr="foo()">
  This is within footag tags...
</%call>

mako10.txt

<%def name="display_n_times_with_style(style, n=1)">
  <p style="${style}">
    % for i in range(n):
      ${caller.body()} <br />
    % endfor
  </p>
</%def>

<%call expr="display_n_times_with_style(style='color: blue')">
  Buy me!
</%call>

<%call expr="display_n_times_with_style(style='color: red', n=3)">
  Buy me now!
</%call>

mako11.txt

<%def name="test(condition)">
  % if condition:
    ${caller.body()}
  % endif
</%def>

<%call expr="test(3+2==5)">
  Python is awesome!
</%call>

<%call expr="test(3+2==10)">
  Python is awful!
</%call>

mako12.txt

<%def name="callee(value_from_caller)">
  I am callee, called with value ${value_from_caller}.
  I'll send the incremented value back to the caller!
  The body of the caller will be:

  ${caller.body(value_from_callee=value_from_caller+1)}
</%def>

<%call expr="callee(42)" args="value_from_callee">
    I am the caller.
    I have been called back by the callee
    with the value ${value_from_callee}.
</%call>

mako13.txt

<%def name="htmlize()">
  <div>
    <div class="header">
      ${caller.header()}
    </div>
  
    <div class="main">
      ${caller.body()}
    </div>

    <div class="footer">
      ${caller.footer()}
    </div>
  </div>
</%def>

<%call expr="htmlize()">
  <%def name="header()">
    This is the header.
  </%def>
  <%def name="footer()">
    This is the footer.
  </%def>

  This is the body.
</%call>

mako14.txt

<%def name="nonbuffered()">
  This is nonbuffered!
</%def>

<%def name="buffered()" buffered="True">
  This is buffered!
</%def>

${" before " + nonbuffered() + " after "}
${" before " + buffered() + " after "}

mako15.txt

<%def name="foobar(a, b)">
  This is foobar(${a}, ${b})
</%def>

${" results " + foobar(5, 10) + " after "}
${" results " + capture(foobar, 5, 10) + " after "}

mako16.txt

<%def name="summer(lst)">
  <%
     result = 0
     for elem in lst:
         result = result + elem
  %>
  The sum of ${lst} is ${result}.
</%def>

<%def name="product(lst)" filter="trim">
  <%
     result = 1
     for elem in lst:
         result = result * elem
  %>
  The product of ${lst} is ${result}
</%def>

Summer  : ${summer([10, 20, 30, 40, 50])}
Product : ${product([10, 20, 30, 40, 50])}

mako17.txt

mako18.txt

Cheetah

URLs:

## A simple multiplication table
<table>
  #for $row in range(int($row_begin), int($row_end)):
  <tr>
    #for $col in range(int($col_begin), int($col_end)):
    <td>${$row * $col}</td>
    #end for
  </tr>
  #end for
</table>

cheetah1.txt

XML-basiertes Templating

Genshi

Screenshots:

URLs:

<html>
  <head><title>$title</title></head>
  <body><h1>$title</h1><p>Hello, $name</p></body>
</html>

genshi1.txt

#!/usr/bin/env python
# showgenshi.py -- render genshi templates

from genshi.template import TemplateLoader, MarkupTemplate

class Renderer(object):
    def __init__(self, dirs='.', variable_lookup='strict'):
        self.loader = TemplateLoader(dirs, variable_lookup=variable_lookup)
        self.variable_lookup = variable_lookup
    
    def r_file(self, templatename, **kwargs):
        self.tmpl = self.loader.load(templatename)
        self.stream = self.tmpl.generate(**kwargs)
        return self.stream.render('xhtml')
    
    def r_str(self, templatestring, **kwargs):
        self.tmpl = MarkupTemplate(templatestring,
                                   lookup=self.variable_lookup)
        self.stream = self.tmpl.generate(**kwargs)
        return self.stream.render('xhtml')
    
if __name__ == '__main__':
    import sys
    r = Renderer('.')
    print r.render(sys.argv[1]),

showgenshi.py

<div>
  <?python
    from genshi.builder import tag
    def greeting(name):
        return tag.b("Hello, %s" % (name,))
  ?>
  ${greeting('world')}
</div>

genshi2.txt

<div>
  <?python
    if defined("name"):
        myname = name
    else:
        myname = 'N/A'
  ?>
  <em>Hello, ${myname}</em>
</div>

genshi3.txt

<div>
  <?python
    if type(name) is not Undefined:
        myname = name
    else:
        myname = 'N/A'
  ?>
  <em>Hello, ${myname}</em>
</div>

genshi4.txt

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:if demo</title></head>
  <body>
    <h1>Genshi py:if demo</h1>

    <py:if test="cond1">
      <p>Condition 1 is met</p>
    </py:if>

    <div py:if="cond2">
      Condition 2 is met
    </div>

  </body>
</html>

genshi5.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:choose demo</title></head>
  <body>
    <h1>Genshi py:choose demo</h1>

    <div py:choose="day">
      <span py:when="1">Monday</span>
      <span py:when="2">Tuesday</span>
      <span py:when="3">Wedneday</span>
      <span py:when="4">Thursday</span>
      <span py:when="5">Friday</span>
      <span py:when="6">Saturday</span>
      <span py:when="7">Sunday</span>
      <span py:otherwise="">Invalid weekday</span>
    </div>

    <div>
      <py:choose test="grade">
        <py:when test="'A'">Excellent</py:when>
        <py:when test="'B'">Good</py:when>
        <py:when test="'C'">Average</py:when>
        <py:when test="'D'">Poor</py:when>
        <py:when test="'F'">Failing</py:when>
        <py:otherwise>Invalid grade</py:otherwise>
      </py:choose>
    </div>

  </body>
</html>

genshi6.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:for demo</title></head>
  <body>
    <h1>Genshi py:for demo</h1>

    <ul>
      <py:for each="item in thelist">
        <li>${item}</li>
      </py:for>
    </ul>

    <ul>
      <li py:for="item in thelist">${item}</li>
    </ul>

  </body>
</html>

genshi7.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:for demo 2</title></head>
  <body>
    <h1>Genshi py:for demo 2</h1>

    <ul py:if="len(thelist) > 0">
      <py:for each="item in thelist">
        <li>${item}</li>
      </py:for>
    </ul>

    <ul py:if="len(thelist) > 0">
      <li py:for="item in thelist">${item}</li>
    </ul>

  </body>
</html>

genshi8.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:for demo 3</title></head>
  <body>
    <h1>Genshi py:for demo 3</h1>

    <table py:if="len(thedict) > 0">
      <py:for each="key, value in thedict.iteritems()">
        <tr>
          <td>${key}</td>
          <td>${value}</td>
        </tr>
      </py:for>
    </table>

  </body>
</html>

genshi9.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:for and py:with demo</title></head>
  <body>
    <h1>Genshi py:for and py:with demo</h1>

    <table py:if="len(thedict) > 0">
      <py:for each="key in sorted(thedict.keys())">
        <tr>
          <td>${key}</td>
          <td>${thedict[key]}</td>
        </tr>
      </py:for>
    </table>

    <table py:if="len(thedict) > 0">
      <py:with vars="keylist = sorted(thedict.keys())">
	<py:for each="idx, key in enumerate(keylist)">
          <tr>
            <td>${idx}</td>
            <td>${key}</td>
            <td>${thedict[key]}</td>
	  </tr>
        </py:for>
      </py:with>
    </table>

  </body>
</html>

genshi10.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:attrs demo</title></head>
  <body>
    <h1>Genshi py:attrs demo</h1>

    <div py:attrs="myattrs">
      Some content
    </div>

  </body>
</html>

genshi11.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
  <head><title>Genshi py:content, py:replace and py:strip demo</title></head>
  <body>
    <h1>Genshi structure demo</h1>

    <div py:content="cont1">
      This will be replaced by cont1
    </div>

    <div>
      <span py:replace="cont2">This will be replaced by cont2</span>
    </div>

    <div>
      <span py:strip="nospan">This is or is not within a span tag</span>
    </div>

  </body>
</html>

genshi12.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      py:strip="True"
      lang="en">
  <body py:strip="True">
    <span py:def="add(a, b)">add(${a}, ${b}) is ${a + b}</span>
    <span py:def="sub(a, b)">sub(${a}, ${b}) is ${a - b}</span>
    <span py:def="mul(a, b)">mul(${a}, ${b}) is ${a * b}</span>
    <span py:def="div(a, b)">div(${a}, ${b}) is ${a / b}</span>

    <span py:def="summer(lst)">
      <?python
          result = 0
          for elem in lst:
              result = result + elem
      ?>
      summer(${str(lst)}) is ${result}
    </span>

    <div py:def="mult_table(row_begin, row_end, col_begin, col_end)">
      <table>
        <py:for each="row in range(row_begin, row_end)">
          <tr>
            <py:for each="col in range(col_begin, col_end)">
              <td>${row * col}</td>
            </py:for>
          </tr>
        </py:for>
      </table>
    </div>
  </body>  
</html>

genshicalc.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      lang="en">
  <head><title>Genshi py:def demo (using functions)</title></head>
  <body>
    <h1>Genshi py:def demo</h1>

    <xi:include href="genshicalc.html" />
    
    <ul>
      <li py:content="add(x1, x2)" />
      <li py:content="sub(x1, x2)" />
      <li py:content="mul(x1, x2)" />
      <li py:content="div(x1, x2)" />
      <li>${summer(thelist)}</li>
    </ul>

    <div py:replace="mult_table(1, 4, 1, 4)" />

  </body>
</html>

genshi13.html

Kid

URLs:

Weitere Templating Systeme

URLs:

Web Frameworks

Django

Screenshots:

Ein Django-Projekt starten

Screenshots:

Eine Datenbank anbinden

Modelle definieren

from django.db import models

class Article(models.Model):
    title     = models.CharField(maxlength=100)
    slug      = models.SlugField(maxlength=50)
    pub_date  = models.DateTimeField('date published')
    author    = models.CharField('by line', maxlength=40)
    content   = models.TextField('the article')

    def __str__(self):
        return self.slug

    class Admin:
        pass

class Talkback(models.Model):
    article   = models.ForeignKey(Article)
    tbauthor  = models.CharField('tb author', maxlength=40)
    tbemail   = models.EmailField('email talkbacker')
    tbloc     = models.CharField('city or country', maxlength=20)
    tbsubject = models.CharField('subject', maxlength=40)
    tbcontent = models.CharField('the talkback', maxlength=250)

    def __str__(self):
        return self.tbsubject

    class Admin:
        pass

models.py

Das Modell ausprobieren

Die öffentliche Sicht

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^articles/$',                     'pypost.articles.views.index'),
    (r'^articles/(?P<article_id>\d+)/$', 'pypost.articles.views.article'),
    (r'^articles/(?P<article_id>\d+)/(?P<talkback_id>\d+)/$',
                                         'pypost.articles.views.talkback'),
    (r'^articles/(?P<article_id>\d+)/talkback/$',
                                         'pypost.articles.views.newtalkback'),
    (r'^articles/(?P<article_id>\d+)/talkback_submit/$',
                                         'pypost.articles.views.savetalkback'),

    (r'^admin/', include('django.contrib.admin.urls')),
)

urls.py

Screenshots:

from django.template import Context, loader
from django.http import HttpResponse
from django.shortcuts import render_to_response

from pypost.articles.models import Article, Talkback

def index(request):
    "Create a list of the 5 latest articles"
    latest_articles_list = Article.objects.all().order_by('-pub_date')[:5]
    t = loader.get_template('articles/index.html')
    c = Context({'latest_articles_list': latest_articles_list,})
    return HttpResponse(t.render(c))

def article(request, article_id):
    "Fetch and show the article with the ID article_id and its talkback list"
    article = Article.objects.get(pk=article_id)
    talkbacks = article.talkback_set.all()
    return render_to_response('articles/article.html',
                              {'article': article, 'talkbacks': talkbacks})

def talkback(request, article_id, talkback_id):
    "Fetch and show a talkback to an article"
    talkback = Talkback.objects.get(pk=talkback_id)
    article  = Article.objects.get(pk=article_id)
    article_title = article.title
    return render_to_response('articles/talkback.html',
                              {'article_title': article_title,
                               'article_id': article_id,
                               'talkback': talkback})

def newtalkback(request, article_id):
    "Present a form for a new talkback to article_id"
    article = Article.objects.get(pk=article_id)
    article_title = article.title
    return render_to_response('articles/newtalkback.html',
                              {'article_title': article_title,
                               'article_id': article_id})

def savetalkback(request, article_id):
    "Saved user-submitted talkback data"
    art = Article.objects.get(pk=article_id)
    art.talkback_set.create(tbauthor=request.POST['author'],
                            tbemail=request.POST['email'],
                            tbloc=request.POST['location'],
                            tbsubject=request.POST['subject'],
                            tbcontent=request.POST['content'])
    return article(request, article_id) # Display article page again.

views.py

<h1>Current articles</h1>

<ul>
{% for article in latest_articles_list %}
  <li>
    <a href="/articles/{{ article.id }}/">
      {{ article.title }}
    </a>
  </li>
{% endfor %}
</ul>

index.html

<h1>{{ article.title }}</h1>

<p><b>By {{ article.author }}</b><br />
{{ article.pub_date }}
</p>

<div>
  {{ article.content }}
</div>

<p>Back to <a href="/articles/">News Summary</a>.</p>

<h2>Talkbacks</h2>

<ul>
{% for tb in talkbacks %}
  <li>
    <a href="/articles/{{ article.id }}/{{ tb.id }}">
      {{ tb.tbsubject }}
    </a>
  </li>
{% endfor %}
</ul>

<p>
  <a href="/articles/{{ article.id }}/talkback/">
    Add Talkback
  </a>
</p>

article.html

Screenshots:

<h1>{{ talkback.tbsubject }}</h1>

<p>Talkback to article:
    <a href="/articles/{{ article_id }}/">{{ article_title }}</a>
</p>

<table border="0">
  <tr>
    <td>Author</td>
    <td>{{ talkback.tbauthor }}</td>
  </tr>
  <tr>
    <td>From</td>
    <td>{{ talkback.tbloc }}</td>
  </tr>
  <tr>
    <td>Subject</td>
    <td>{{ talkback.tbsubject }}</td>
  </tr>
</table>

<p>&nbsp;</p>

<div>
  {{ talkback.tbcontent }}
</div>

talkback.html

<h1>Add Talkback</h1>

<p>Add Talkback to article:
    <a href="/articles/{{ article_id }}/">{{ article_title }}</a>
</p>

<form action="/articles/{{ article_id }}/talkback_submit/" method="POST">
  <table border="0">
    <tr>
      <td>Your name</td>
      <td><input type="text" name="author" size="40" value="" /></td>
    </tr>
    <tr>
      <td>Your email</td>
      <td><input type="text" name="email" size="20" value="" />
          (<b>not</b> for publication)</td>
    </tr>
    <tr>
      <td>Country / City</td>
      <td><input type="text" name="location" size="20" value="" /></td>
    </tr>
    <tr>
      <td>Subject</td>
      <td><input type="text" name="subject" size="40" value="" /></td>
    </tr>
    <tr valign="top">
      <td>Your comments</td>
      <td><textarea name="content" cols="40" rows="7" value=""></textarea>
          (250 characters max.)</td>
    </tr>
  </table>
  <br />
  <input type="submit" value="Submit Talkback" />
  <input type="reset"  value="Clear fields" />
</form>

newtalkback.html

Was noch zu berücksichtigen ist

Weitere Web Frameworks

URLs:

Zope, Plone et. al.

Screenshots:

URLs:

Zope und Plone zusammen installieren

Erste Schritte in Zope

Screenshots:

Erzeugt man ein Script (Python), erhält man von Zope erst folgenden Anfangscode:

# Example code:

# Import a standard function, and get the HTML request and response objects.
from Products.PythonScripts.standard import html_quote
request = container.REQUEST
RESPONSE =  request.RESPONSE

# Return a string identifying this script.
print "This is the", script.meta_type, '"%s"' % script.getId(),
if script.title:
    print "(%s)" % html_quote(script.title),
print "in", container.absolute_url()
return printed

Wir könnten es z.B. so verändern:

request = container.REQUEST

print "We've got the following container.REQUEST object:"
print request

return printed

Der Webrechner in Zope besteht aus einem ZPT zopecalc_pt, das so aussieht:

<html>
  <head>
    <title tal:content="template/title">The title</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
  </head>
  <body>
    <form action="zopecalc" method='POST'>
      <input type="text" size="20" name="arg1" value="number 1" />
      <select name="op">
        <option>add</option>
        <option>sub</option>
        <option>mul</option>
        <option>div</option>
      </select>
      <input type="text" size="20" name="arg2" value="number 2" />
      <br />
      <input type="submit" value="Calculate" />
      <input type="reset" value="Clear fields" />
    </form>
  </body>
</html>

und aus dem dazu passenden Script (Python) zopecalc, der davon aufgerufen wird und so aussieht:

if len(traverse_subpath) == 3:
    # extract arguments from PATH_INFO...
    op, arg1, arg2 = traverse_subpath
else:
    # or from GET or POSt parameters.
    op   = container.REQUEST.form.get('op', None)
    arg1 = container.REQUEST.form.get('arg1', None)
    arg2 = container.REQUEST.form.get('arg2', None)

# called the first time w/o args? send form to user
if op is None or arg1 is None or arg2 is None:
    pt = context.zopecalc_pt
    return pt()

if op not in ('add', 'sub', 'mul', 'div'):
    return 'Invalid operator. Use one of add, sub, mul or div'

result = 'No result yet'
try:
    numarg1 = float(arg1)
    numarg2 = float(arg2)
    if op == 'add': result = numarg1 + numarg2
    elif op == 'sub': result = numarg1 - numarg2
    elif op == 'mul': result = numarg1 * numarg2
    elif op == 'div':
        if numarg2 == 0: result = 'NaN'
        else:            result = numarg1 / numarg2
except ValueError:
    return 'Invalid arguments. Use only numerical arguments.'
except TypeError:
    return 'Invalid arguments. Missing one or both argument.'

return str(result)

Macros in Plone

Das Script (Python) plonecalc sieht so aus:

if len(traverse_subpath) == 3:
    # extract arguments from PATH_INFO...
    op, arg1, arg2 = traverse_subpath
else:
    # or from GET or POSt parameters.
    op   = container.REQUEST.form.get('op', None)
    arg1 = container.REQUEST.form.get('arg1', None)
    arg2 = container.REQUEST.form.get('arg2', None)

# This will be our return template
pt = context.plonecalc_pt

# called the first time w/o args? send form to user
if op is None or arg1 is None or arg2 is None:
    return pt()

if op not in ('add', 'sub', 'mul', 'div'):
    return pt(result='Invalid operator. Use one of add, sub, mul or div')

result = 'No result yet'
try:
    numarg1 = float(arg1)
    numarg2 = float(arg2)
    if op == 'add': result = numarg1 + numarg2
    elif op == 'sub': result = numarg1 - numarg2
    elif op == 'mul': result = numarg1 * numarg2
    elif op == 'div':
        if numarg2 == 0: result = 'NaN'
        else:            result = numarg1 / numarg2
except ValueError:
    return pt(result='Invalid arguments. Use only numerical arguments.')
except TypeError:
    return pt(result='Invalid arguments. Missing one or both argument.')

# Now return result in a template
return pt(result=str(result))

Das dazu passende Zope Page Template plone_calc_pt lautet:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      lang="en"
      metal:use-macro="here/main_template/macros/master"
      i18n:domain="plone">

<body>
  <div metal:fill-slot="main">

    <h1>The Plone Calculator</h1>

    <div tal:condition="exists:options/result">

      <h2>Previous results</h2>
      The result of the previous calculation was
      <b tal:content="options/result">
        placeholder for result
      </b>.

      <h2>New calculation</h2>
    </div>

    <form method="POST" action="plonecalc">
      <input type="text" size="20" name="arg1" value="111" />
      <select name="op">

        <option>add</option>
        <option>sub</option>
        <option>mul</option>
        <option>div</option>
      </select>
      <input type="text" size="20" name="arg2" value="222" />
      <br />

      <input type="submit" value="Calculate" />
      <input type="reset" value="Clear fields" />
    </form>
  </div>

</body>

</html>

Screenshot:

Wikis

URLs:

Zusammenfassung