forked from grisha/mod_python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodpython6.tex
More file actions
610 lines (467 loc) · 20.2 KB
/
Copy pathmodpython6.tex
File metadata and controls
610 lines (467 loc) · 20.2 KB
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
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
\chapter{Server Side Includes\label{ssi}}
\section{Overview of SSI\label{ssi-overview}}
SSI (Server Side Includes) are directives that are placed in HTML pages,
and evaluated on the server while the pages are being served. They let you
add dynamically generated content to an existing HTML page, without having
to serve the entire page via a CGI program, or other dynamic technology
such as a mod_python handler.
SSI directives have the following syntax:
\begin{verbatim}
<!--#element attribute=value attribute=value ... -->
\end{verbatim}
It is formatted like an HTML comment, so if you don't have SSI correctly
enabled, the browser will ignore it, but it will still be visible in the
HTML source. If you have SSI correctly configured, the directive will be
replaced with its results.
For a more thorough description of the SSI mechanism and how to enable it,
see the \citetitle[http://httpd.apache.org/docs/2.0/howto/ssi.html]{SSI
tutorial} provided with the Apache documentation.
Version 3.3 of mod_python introduces support for using Python code within
SSI files. Note that mod_python honours the intent of the Apache
\code{IncludesNOEXEC} option to the \code{Options} directive. That is, if
\code{IncludesNOEXEC} is enabled, then Python code within a SSI file will
not be executed.
\section{Using Python Code\label{ssi-python-code}}
The extensions to mod_python to allow Python code to be used in conjunction
with SSI introduces the new SSI directive called 'python'. This directive
can be used in two forms, these being 'eval' and 'exec' modes. In the case
of 'eval', a Python expression is used and it is the result of that
expression which is substituted in place into the page.
\begin{verbatim}
<!--#python eval="10*'HELLO '" -->
<!--#python eval="len('HELLO')" -->
\end{verbatim}
Where the result of the expression is not a string, the value will be
automatically converted to a string by applying \code{str()} to the value.
In the case of 'exec' a block of Python code may be included. For any
output from this code to appear in the page, it must be written back
explicitly as being part of the response. As SSI are processed by an Apache
output filter, this is done by using an instance of the mod_python
\code{filter} object which is pushed into the global data set available to
the code.
\begin{verbatim}
<!--#python exec="
filter.write(10*'HELLO ')
filter.write(str(len('HELLO')))
" -->
\end{verbatim}
Any Python code within the 'exec' block must have a zero first level
indent. You cannot start the code block with an arbitrary level of indent
such that it lines up with any indenting used for surrounding HTML
elements.
Although the mod_python \code{filter} object is not a true file object, that
it provides the \code{write()} method is sufficient to allow the \code{print}
statement to be used on it directly. This will avoid the need to explicitly
convert non string objects to a string before being output.
\begin{verbatim}
<!--#python exec="
print >> filter, len('HELLO')
" -->
\end{verbatim}
\section{Scope of Global Data\label{ssi-data-scope}}
Multiple instances of 'eval' or 'exec' code blocks may be used within the
one page. Any changes to or creation of global data which is performed
within one code block will be reflected in any following code blocks.
Global data constitutes variables as well as module imports, function and
class definitions.
\begin{verbatim}
<!--#python exec="
import cgi, time, os
def _escape(object):
return cgi.escape(str(object))
now = time.time()
" -->
<html>
<body>
<pre>
<!--#python eval="_escape(time.asctime(time.localtime(now)))"-->
<!--#python exec="
keys = os.environ.keys()
keys.sort()
for key in keys:
print >> filter, _escape(key),
print >> filter, '=',
print >> filter, _escape(repr(os.environ.get(key)))
" -->
</pre>
</body>
</html>
\end{verbatim}
The lifetime of any global data is for the current request only. If data
must persist between requests, it must reside in external modules and as
necessary be protected against multithreaded access in the event that a
multithreaded Apache MPM is used.
\section{Pre-propulating Globals\label{ssi-globals}}
Any Python code which appears within the page has to be compiled each time
the page is accessed before it is executed. In other words, the compiled
code is not cached between requests. To limit such recompilation and to
avoid duplication of common code amongst many pages, it is preferable to
pre-populate the global data from within a mod_python handler prior to the
page being processed.
In most cases, a fixup handler would be used for this purpose. For this to
work, first need to configure Apache so that it knows to call the fixup
handler.
\begin{verbatim}
PythonFixupHandler _handlers
\end{verbatim}
The implementation of the fixup handler contained in \code{_handlers.py}
then needs to create an instance of a Python dictionary, store that in the
mod_python request object as \code{ssi_globals} and then populate that
dictionary with any data to be available to the Python code executing
within the page.
\begin{verbatim}
from mod_python import apache
import cgi, time
def _escape(object):
return cgi.escape(str(object))
def _header(filter):
print >> filter, '...'
def _footer(filter):
print >> filter, '...'
def fixuphandler(req):
req.ssi_globals = {}
req.ssi_globals['time'] = time
req.ssi_globals['_escape'] = _escape
req.ssi_globals['_header'] = _header
req.ssi_globals['_footer'] = _footer
return apache.OK
\end{verbatim}
This is most useful where it is necessary to insert common information such
as headers, footers or menu panes which are dynamically generated into many
pages.
\begin{verbatim}
<!--#python exec="
now = time.time()
" -->
<html>
<body>
<!--#python exec="_header(filter)" -->
<pre>
<!--#python eval="_escape(time.asctime(time.localtime(now)))"-->
</pre>
<!--#python exec="_footer(filter)" -->
</body>
</html>
\end{verbatim}
\section{Conditional Expressions\label{ssi-conditionals}}
SSI allows for some programmability in its own right through the use of
conditional expressions. The structure of this conditional construct is:
\begin{verbatim}
<!--#if expr="test_condition" -->
<!--#elif expr="test_condition" -->
<!--#else -->
<!--#endif -->
\end{verbatim}
A test condition can be any sort of logical comparison, either comparing
values to one another, or testing the 'truth' of a particular value.
The source of variables used in conditional expressions is distinct from
the set of global data used by the Python code executed within a page.
Instead, the variables are sourced from the \code{subprocess_env} table
object contained within the request object. The values of these variables
can be set from within a page using the SSI 'set' directive, or by a range
of other Apache directives within the Apache configuration files such as
\code{BrowserMatchNoCase} and \code{SetEnvIf}.
To set these variables from within a mod_python handler, the
\code{subprocess_env} table object would be manipulated directly through
the request object.
\begin{verbatim}
from mod_python import apache
def fixuphandler(req):
debug = req.get_config().get('PythonDebug', '0')
req.subprocess_env['DEBUG'] = debug
return apache.OK
\end{verbatim}
If being done from within Python code contained within the page itself, the
request object would first have to be accessed via the filter object.
\begin{verbatim}
<!--#python exec="
debug = filter.req.get_config().get('PythonDebug', '0')
filter.req.subprocess_env['DEBUG'] = debug
" -->
<html>
<body>
<!--#if expr="${DEBUG} != 0" -->
DEBUG ENABLED
<!--#else -->
DEBUG DISABLED
<!--#endif -->
</body>
</html>
\end{verbatim}
\section{Enabling INCLUDES Filter\label{ssi-output-filter}}
SSI is traditionally enabled using the \code{AddOutputFilter} directive in
the Apache configuration files. Normally this would be where the request
mapped to a static file.
\begin{verbatim}
AddOutputFilter INCLUDES .shtml
\end{verbatim}
When mod_python is being used, the ability to dynamically enable output
filters for the current request can instead be used. This could be done
just for where the request maps to a static file, but may just as easily be
carried out where the content of a response is generated dynamically. In
either case, to enable SSI for the current request, the
\code{add_output_filter()} method of the mod_python request object would be
used.
\begin{verbatim}
from mod_python import apache
def fixuphandler(req):
req.add_output_filter('INCLUDES')
return apache.OK
\end{verbatim}
\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 mod_python
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{req.filename}
attribute. Before importing, the file extension, if any, is
discarded.
If \class{req.filename} is empty, the module name defaults to
\samp{index}.
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.
If no path_info was given in the URL, the Publisher handler will use
the default value of \samp{index}. If the last element is an object inside
a module, and the one immediately preceding it is a directory
(i.e. no module name is given), then the module name will also default
to \samp{index}.
The traversal will stop and \constant{HTTP_NOT_FOUND} 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.
For example, given the following configuration:
\begin{verbatim}
DocumentRoot /some/dir
<Directory /some/dir>
SetHandler mod_python
PythonHandler mod_python.publisher
</Directory>
\end{verbatim}
And the following \file{/some/dir/index.py} file:
\begin{verbatim}
def index(req):
return "We are in index()"
def hello(req):
return "We are in hello()"
\end{verbatim}
Then:
http://www.somehost/index/index will return \samp{We are in index()}
http://www.somehost/index/ will return \samp{We are in index()}
http://www.somehost/index/hello will return \samp{We are in hello()}
http://www.somehost/hello will return \samp{We are in hello()}
http://www.somehost/spam will return \samp{404 Not Found}
\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. Any fields whose names do
not match the names of callable argument objects will be silently dropped,
unless the destination callable object has a \code{**kwargs} style
argument, in which case fields with unmatched names will be passed in the
\code{**kwargs} argument.
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 \samp{eggs} with password \samp{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.
\begin{notice}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.
\end{notice}
\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 attempt to instantiate \class{FieldStorage} when
using the Publisher handler and should use
\class{Request.form} instead.
\section{PSP Handler\label{hand-psp}}
\index{PSP}
PSP handler is a handler that processes documents using the
\class{PSP} class in \code{mod_python.psp} module.
To use it, simply add this to your httpd configuration:
\begin{verbatim}
AddHandler mod_python .psp
PythonHandler mod_python.psp
\end{verbatim}
For more details on the PSP syntax, see Section \ref{pyapi-psp}.
If \code{PythonDebug} server configuration is \code{On}, then by
appending an underscore (\samp{_}) to the end of the url you can get a
nice side-by-side listing of original PSP code and resulting Python
code generated by the \code{psp} module. This is very useful for
debugging. You'll need to adjust your httpd configuration:
\begin{verbatim}
AddHandler mod_python .psp .psp_
PythonHandler mod_python.psp
PythonDebug On
\end{verbatim}
\begin{notice}
Leaving debug on in a production environment will allow remote users
to display source code of your PSP pages!
\end{notice}
\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 \samp{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 \samp{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. This is because the CGI environment was not intended for
execution within threads (e.g. requires changing of current directory
with is inherently not thread-safe, so to overcome this cgihandler
maintains a thread lock which forces it to process one request at a
time in a multi-threaded server) and therefore can only be implemented
in a way that defeats many of the advantages of using mod_python in
the first place.
To use it, simply add this to your \file{.htaccess} file:
\begin{verbatim}
SetHandler mod_python
PythonHandler mod_python.cgihandler
\end{verbatim}
As of version 2.7, the cgihandler will properly reload even indirectly
imported module. 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.