1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
|
\chapter{Standard Handlers\label{handlers}}
\section{Publisher Handler\label{hand-pub}}
The \code{publisher} handler is a good way to avoid writing your own
handlers and focus on rapid application development. It was inspired
by \citetitle[http://www.zope.org/]{Zope} ZPublisher.
\subsection{Introduction\label{hand-pub-intro}}
To use the handler, you need the following lines in your configuration
\begin{verbatim}
<Directory /some/path}
SetHandler python-program
PythonHandler mod_python.publisher
</Directory>
\end{verbatim}
This handler allows access to functions and variables within a module
via URL's. For example, if you have the following module, called
\file{hello.py}:
\begin{verbatim}
""" Publisher example """
def say(req, what="NOTHING"):
return "I am saying %s" % what
\end{verbatim}
A URL \code{http://www.mysite.com/hello.py/say} would return
\samp{I am saying NOTHING}. A URL
\code{http://www.mysite.com/hello.py/say?what=hello} would
return \samp{I am saying hello}.
\subsection{The Publishing Algorithm\label{hand-pub-alg}}
The Publisher handler maps a URI directly to a Python variable or
callable object, then, respectively, returns it's string
representation or calls it returning the string representation of the
return value.
\subsubsection{Traversal\label{hand-pub-alg-trav}}
The Publisher handler locates and imports the module specified in the
URI. The module location is determined from the
\class{Request.filename} attribute. Before importing, the file extension,
if any, is discarded.
Once module is imported, the remaining part of the URI up to the
beginning of any query data (a.k.a. PATH_INFO) is used to find an
object within the module. The Publisher handler \dfn{traverses} the
path, one element at a time from left to right, mapping the elements
to Python object within the module.
The traversal will stop and \constant{HTTP_NOTFOUND} will be returned to
the client if:
\begin{itemize}
\item
Any of the traversed object's names begin with an underscore
(\samp{\_}). Use underscores to protect objects that should not be
accessible from the web.
\item
A module is encountered. Published objects cannot be modules for
security reasons.
\end{itemize}
If an object in the path could not be found, \constant{HTTP_NOT_FOUND}
is returned to the client.
\subsubsection{Argument Matching and Invocation\label{hand-pub-alg-args}}
Once the destination object is found, if it is callable and not a
class, the Publisher handler will get a list of arguments that the
object expects. This list is compared with names of fields from HTML
form data submitted by the client via \code{POST} or
\code{GET}. Values of fields whose names match the names of callable
object arguments will be passed as strings.
If the destination is not callable or is a class, then its string
representation is returned to the client.
\subsubsection{Authentication\label{hand-pub-alg-auth}}
The publisher handler provides simple ways to control access to
modules and functions.
At every traversal step, the Publisher handler checks for presence of
\method{__auth__} and \method{__access__} attributes (in this order), as
well as \method{__auth_realm__} attribute.
If \method{__auth__} is found and it is callable, it will be called
with three arguments: the \class{Request} object, a string containing
the user name and a string containing the password. If the return
value of
\code{__auth__} is false, then \constant{HTTP_UNAUTHORIZED} is
returned to the client (which will usually cause a password dialog box
to appear).
If \method{__auth__} is a dictionary, then the user name will be
matched against the key and the password against the value associated
with this key. If the key and password do not match,
\constant{HTTP_UNAUTHORIZED} is returned. Note that this requires
storing passwords as clear text in source code, which is not very secure.
\method{__auth__} can also be a constant. In this case, if it is false
(i.e. \constant{None}, \code{0}, \code{""}, etc.), then
\constant{HTTP_UNAUTHORIZED} is returned.
If there exists an \code{__auth_realm__} string, it will be sent
to the client as Authorization Realm (this is the text that usually
appears at the top of the password dialog box).
If \method{__access__} is found and it is callable, it will be called
with two arguments: the \class{Request} object and a string containing
the user name. If the return value of \code{__access__} is false, then
\constant{HTTP_FORBIDDEN} is returned to the client.
If \method{__access__} is a list, then the user name will be matched
against the list elements. If the user name is not in the list,
\constant{HTTP_FORBIDDEN} is returned.
Similarly to \method{__auth__}, \method{__access__} can be a constant.
In the example below, only user "eggs" with password "spam" can access
the \code{hello} function:
\begin{verbatim}
__auth_realm__ = "Members only"
def __auth__(req, user, passwd):
if user == "eggs" and passwd == "spam" or \
user == "joe" and passwd == "eoj":
return 1
else:
return 0
def __access__(req, user):
if user == "eggs":
return 1
else:
return 0
def hello(req):
return "hello"
\end{verbatim}
Here is the same functionality, but using an alternative technique:
\begin{verbatim}
__auth_realm__ = "Members only"
__auth__ = {"eggs":"spam", "joe":"eoj"}
__access__ = ["eggs"]
def hello(req):
return "hello"
\end{verbatim}
Since functions cannot be assigned attributes, to protect a function,
an \code{__auth__} or \code{__access__} function can be defined within
the function, e.g.:
\begin{verbatim}
def sensitive(req):
def __auth__(req, user, password):
if user == 'spam' and password == 'eggs':
# let them in
return 1
else:
# no access
return 0
# something involving sensitive information
return 'sensitive information`
\end{verbatim}
Note that this technique will also work if \code{__auth__} or
\code{__access__} is a constant, but will not work is they are
a dictionary or a list.
The \code{__auth__} and \code{__access__} mechanisms exist
independently of the standard
\citetitle[dir-handlers-auh.html]{PythonAuthenHandler}. It
is possible to use, for example, the handler to authenticate, then the
\code{__access__} list to verify that the authenticated user is
allowed to a particular function.
\strong{NOTE:} In order for mod_python to access \function{__auth__},
the module containing it must first be imported. Therefore, any
module-level code will get executed during the import even if
\function{__auth__} is false. To truly protect a module from
being accessed, use other authentication mechanisms, e.g. the Apache
\code{mod_auth} or with a mod_python \citetitle[dir-handlers-auh.html]
{PythonAuthenHandler} handler.
\subsection{Form Data}
In the process of matching arguments, the Publisher handler creates an
instance of \citetitle[pyapi-util-fstor.html]{FieldStorage}
class. A reference to this instance is stored in an attribute \member{form}
of the \class{Request} object.
Since a \class{FieldStorage} can only be instantiated once per
request, one must not attept to instantiate \class{FieldStorage} when
using the Publisher handler and should use
\class{Request.form} instead.
\section{CGI Handler\label{hand-cgi}}
\index{CGI}
CGI handler is a handler that emulates the CGI environment under mod_python.
Note that this is not a "true" CGI environment in that it is emulated
at the Python level. \code{stdin} and \code{stdout} are provided by
substituting \code{sys.stdin} and \code{sys.stdout}, and the environment
is replaced by a dictionary. The implication is that any outside programs
called from within this environment via \code{os.system}, etc. will
not see the environment available to the Python program, nor will they
be able to read/write from standard input/output with the results expected
in a "true" CGI environment.
The handler is provided as a stepping stone for the migration of legacy
code away from CGI. It is not recommended that you settle on using
this handler as the preferred way to use mod_python for the long term.
To use it, simply add this to your \file{.htaccess} file:
\begin{verbatim}
SetHandler python-program
PythonHandler mod_python.cgihandler
\end{verbatim}
As of version 2.7, the cgihandler will properly reload even indirectly
imported modules. This is done by saving a list of loaded modules
(sys.modules) prior to executing a CGI script, and then comparing it
with a list of imported modules after the CGI script is done. Modules
(except for whose whose __file__ attribute points to the standard
Python library location) will be deleted from sys.modules thereby
forcing Python to load them again next time the CGI script imports
them.
If you do not want the above behavior, edit the \file{cgihandler.py}
file and comment out the code delimited by \#\#\#.
Tests show the cgihandler leaking some memory when processing a lot of
file uploads. It is still not clear what causes this. The way to work
around this is to set the Apache \code{MaxRequestsPerChild} to a non-zero
value.
\section{Httpdapy handler\label{hand-httpdapy}}
This handler is provided for people migrating from Httpdapy. To use
it, add this to your \code{.htaccess} file:
\begin{verbatim}
PythonHandler mod_python.httpdapi
\end{verbatim}
You will need to change one line in your code. Where it said
\begin{verbatim}
import httpdapi
\end{verbatim}
it now needs to say
\begin{verbatim}
from mod_python import httpdapi
\end{verbatim}
If you were using authentication, in your .htaccess, instead of:
\begin{verbatim}
AuthPythonModule modulename
\end{verbatim}
use
\begin{verbatim}
PythonOption authhandler modulename
\end{verbatim}
NB: Make sure that the old httpdapi.py and apache.py are not in your
python path anymore.
\section{ZHandler\label{hand-z}}
\strong{NOTE:} This handler is being phased out in favor of the
\citetitle[hand-pub.html]{Publisher} handler described in
Section \ref{hand-pub}.
This handler allows one to use the Z Object Publisher (formerly Bobo)
with mod_python. This gives you the power of Zope Object Publishing
along with the speed of mod_python. It doesn't get any better than
this!
WHAT IS ZPublisher?
ZPublisher is a component of Zope. While I don't profess at Zope
itself as it seems to be designed for different type of users than me,
I do think that the ZPublisher provides an ingeniously simple way of
writing WWW applications in Python.
A quick example do demonstrate the power of ZPublisher.
Suppose you had a file called zhello.py like this:
\begin{verbatim}
"""A simple Bobo application"""
def sayHello( name = "World" ):
""" Sais Hello (this comment is required)"""
return "Hello %s!" % name
\end{verbatim}
Notice it has one method defined in it. Through ZPublisher, that
method can be invoked through the web via a URL similar to this:
http://www.domain.tld/site/zhello/sayHello and \\
http://www.domain.tld/site/zhello/sayHello?name=Joe
Note how the query keyword "name" converted to a keyword argument to
the function.
If the above didn't "click" for you, go read the ZPublisher
documentation at
http://classic.zope.org:8080/Documentation/Reference/ObjectPublishingIntro
for a more in-depth explanation.
QUICK START
\begin{enumerate}
\item
Download and install Zope.
\item
Don't start it. You're only interested in ZPublisher, and in order for
it to work, Zope doesn't need to be running.
\item
Pick a www directory where you want to use ZPublisher. For our purposes
let's imagine it is accessible via http://www.domain.tld/site.
\item
Make sure that the FollowSymLinks option is on for this directory
in httpd.conf.
\item
Make a symlink in this directory to the ZPublisher directory:
\begin{verbatim}
cd site
ln -s /usr/local/src/Zope-2.1.0-src/lib/python/ZPublisher .
\end{verbatim}
\item
Verify that it is correct:
\begin{verbatim}
ls -l
lrwxr-xr-x 1 uid group 53 Dec 13 12:15 ZPublisher -> /usr/local/src/Zope-2.1.0-src/lib/python/ZPublisher
\end{verbatim}
\item
Create an \file{.htaccess} file with this in it:
\begin{verbatim}
SetHandler python-program
PythonHandler mod_python.zhandler
PythonDebug
\end{verbatim}
\item
Create an above mentioned zhello.py file.
\item
Look at http://www.domain.tld/site/zhello/sayHello?name=Joe
\end{enumerate}
Noteworthy:
This module automatically reloads modules just like any other
mod_python module. But modules that are imported by your code will not
get reloaded. There are ways around having to restart the server for
script changes to take effect. For example, let's say you have a
module called mycustomlib.py and you have a module that imports it. If
you make a changes to mycustomlib.py, you can force the changes to
take effect by requesting http://www.domain.tld/site/mycustomlib/.
You will get a server error, but mycustomelib should get reloaded.
P.S.: ZPublisher is not Zope, but only part of it. As of right now, as
far as I know, Zope will not work with mod_python. This is because of
locking issues. Older versions of Zope had no locking, so different
children of apache would corrupt the database by trying to access it
at the same time. Starting with version 2 Zope does have locking,
however, it seems that the first child locks the database without ever
releasing it and after that no other process can access it.
If this is incorrect, and you can manage to get Zope to work without
problems, please send me an e-mail and I will correct this
documentation.
|