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()
#!/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()
#!/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()
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()
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()
#!/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])
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()
#!/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)
Webserver aus Drittanbietermodulen¶
Webserver mit CherryPy¶
URLs:
- Die CherryPy Website (einfacher: mit
easy_install CherryPy
installieren) - Manpage des Programms fetch
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')
#!/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')
#!/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')
#!/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')
#!/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')
#!/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')
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()
#!/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()
#!/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()
#!/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()
#!/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()
#!/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()
#!/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()
#!/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()
#!/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()
#!/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()
#!/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()
print "Content-Type: text/plain\r\n";
print "\r\n";
print "I am a perl script\r\n";
<?php
print "Content-Type: text/plain\r\n";
print "\r\n";
phpinfo();
?>
#!/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()
#!/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)
Integration mit anderen Webservern¶
Lighttpd¶
URLs:
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 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"
FastCGI-Server in Python mit flup¶
URLs:
- Das flup Modul (mit
easy_install flup
installieren)
#!/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()
#!/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()
#!/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()
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"
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()
Python in Lighttpd integrieren¶
Apache¶
URLs:
Apache und CGI¶
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)
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]
#!/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()]
from wsgidebug import debug_app as application
from wsgicalc import webcalc as application
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]
# 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
#!/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")
#!/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()
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")
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])
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()
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>
#!/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')
<!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>
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)
#!/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)
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)
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)
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)
Screenshots:
Den Zustand fälschungssicherer machen¶
Screenshots:
- Man kann Cookies browserseite fälschen
- Das Cookie aus cgistate_cryptcookies.py in Elinks
- Vor der Fälschung des Cookies
- Das Cookie wird gefälscht
- Die Fälschung wird erkannt
URLs:
- Das Web Developer Firefox Plugin
- Die pycrypto Website (mit
easy_install pycrypto
installieren) - A Secure Cookie Protocol by Alex X. Liu et. al. (PDF)
#!/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)
#!/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]
#!/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,)
#!/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!
#!/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)
#!/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.
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')
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()
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)
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()
Templating Engines¶
URLs:
Templating für arme Leute¶
Stringinterpolation¶
From: %(from)s
To: %(to)s
Subject: %(subject)s
You are %(age)d years old.
From: %(from)s
To: %(to)s
Cc: %(cc)s
Subject: %(subject)s
You are %(age)d years old.
From: %(from)s
To: %(to)s%(cc)s
Subject: %(subject)s
You are %(age)d years old.
Text-basiertes Templating¶
Mako¶
Screenshots:
URLs:
- Website des Mako Moduls (aber mit
easy_install Mako
installieren)
From: ${fromvar}
To: ${to}
Subject: ${subject}
${body}
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()
<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
</body>
</html>
<p>${a} + ${b} = ${a + b}</p>
<%include file="mako_header.txt"/>
<%include file="mako_main.txt"/>
<%include file="mako_footer.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]),
## 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>
<%!
import time
%>
From: ${fromvar}
To: ${to}
% if cc != UNDEFINED:
CC: ${cc}
% endif
Date: ${time.ctime()}
Subject: ${subject}
${body}
<%!
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}
% 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
<%!
def foo():
return "I am foo"
%>
<%
foosaid = foo()
%>
Foo said: ${foo()}.
Foo said: ${foosaid}.
<%!
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}
Calling adder(3, 4): ${adder(3, 4)}
<%def name="adder(a, b)">
The sum of ${a} and ${b} is ${a + b}
</%def>
<%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>
<%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])}
<%def name="foo()">
<footag>
${caller.body()}
</footag>
</%def>
<%call expr="foo()">
This is within footag tags...
</%call>
<%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>
<%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>
<%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>
<%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>
<%def name="nonbuffered()">
This is nonbuffered!
</%def>
<%def name="buffered()" buffered="True">
This is buffered!
</%def>
${" before " + nonbuffered() + " after "}
${" before " + buffered() + " after "}
<%def name="foobar(a, b)">
This is foobar(${a}, ${b})
</%def>
${" results " + foobar(5, 10) + " after "}
${" results " + capture(foobar, 5, 10) + " after "}
<%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])}
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>
XML-basiertes Templating¶
Genshi¶
Screenshots:
URLs:
- Website von Genshi (aber mit
easy_install Genshi
installieren)
<html>
<head><title>$title</title></head>
<body><h1>$title</h1><p>Hello, $name</p></body>
</html>
#!/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]),
<div>
<?python
from genshi.builder import tag
def greeting(name):
return tag.b("Hello, %s" % (name,))
?>
${greeting('world')}
</div>
<div>
<?python
if defined("name"):
myname = name
else:
myname = 'N/A'
?>
<em>Hello, ${myname}</em>
</div>
<div>
<?python
if type(name) is not Undefined:
myname = name
else:
myname = 'N/A'
?>
<em>Hello, ${myname}</em>
</div>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
Web Frameworks¶
Django¶
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
Das Modell ausprobieren¶
Die Admin-Site¶
Screenshots:
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')),
)
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.
<h1>Current articles</h1>
<ul>
{% for article in latest_articles_list %}
<li>
<a href="/articles/{{ article.id }}/">
{{ article.title }}
</a>
</li>
{% endfor %}
</ul>
<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>
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> </p>
<div>
{{ talkback.tbcontent }}
</div>
<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>
Was noch zu berücksichtigen ist¶
Zope, Plone et. al.¶
Screenshots:
URLs:
Zope und Plone zusammen installieren¶
Erste Schritte in Zope¶
Screenshots:
- Das Zope Management Interface (ZMI)
- Das Zope Template editieren
- Das Demo Template editieren
- Gebundene Variablen (Bindings)
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:
Lose Enden¶
URLs: